From 7f1e57567e851d1d36630cef9d03ab58eb0052b8 Mon Sep 17 00:00:00 2001 From: Matty Noble Date: Thu, 14 Jul 2011 15:22:37 -0700 Subject: [PATCH 0001/1398] first commit --- README | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README diff --git a/README b/README new file mode 100644 index 00000000000..e69de29bb2d From b666204c185f9e0c82f930887d8e955d9a6cea36 Mon Sep 17 00:00:00 2001 From: amazonwebservices Date: Thu, 14 Jul 2011 15:37:53 -0700 Subject: [PATCH 0002/1398] Version 1.0.0 of the AWS SDK for Ruby --- .yardopts | 6 + Gemfile | 58 + Gemfile.lock | 115 + LICENSE.txt | 171 + NOTICE.txt | 2 + README.rdoc | 189 + Rakefile | 21 + features/ec2/combined.feature | 39 + features/ec2/elastic_ips.feature | 49 + features/ec2/errors.feature | 28 + features/ec2/image_attributes.feature | 285 + features/ec2/images.feature | 122 + features/ec2/instance_attributes.feature | 409 + features/ec2/instances.feature | 174 + features/ec2/key_pairs.feature | 89 + features/ec2/regions.feature | 53 + features/ec2/reserved_instances.feature | 68 + features/ec2/security_groups.feature | 111 + features/ec2/snapshot_attributes.feature | 76 + features/ec2/snapshots.feature | 97 + features/ec2/step_definitions/combined.rb | 42 + features/ec2/step_definitions/ec2.rb | 101 + features/ec2/step_definitions/elasic_ips.rb | 54 + features/ec2/step_definitions/errors.rb | 26 + .../ec2/step_definitions/image_attributes.rb | 108 + features/ec2/step_definitions/images.rb | 103 + .../step_definitions/instance_attributes.rb | 207 + features/ec2/step_definitions/instances.rb | 130 + features/ec2/step_definitions/key_pairs.rb | 74 + features/ec2/step_definitions/regions.rb | 51 + .../step_definitions/reserved_instances.rb | 51 + .../ec2/step_definitions/security_groups.rb | 128 + .../step_definitions/snapshot_attributes.rb | 89 + features/ec2/step_definitions/snapshots.rb | 62 + features/ec2/step_definitions/tagging.rb | 111 + .../ec2/step_definitions/volume_attributes.rb | 30 + features/ec2/step_definitions/volumes.rb | 134 + features/ec2/step_definitions/zones.rb | 34 + features/ec2/tagging.feature | 106 + features/ec2/volume_attributes.feature | 43 + features/ec2/volumes.feature | 191 + features/ec2/zones.feature | 35 + features/record/count.feature | 95 + features/record/dirty_tracking.feature | 69 + features/record/find.feature | 108 + features/record/optimistic_locking.feature | 84 + features/record/scope.feature | 118 + features/record/step_definitions/count.rb | 16 + .../record/step_definitions/dirty_tracking.rb | 28 + .../step_definitions/optimistic_locking.rb | 21 + features/record/step_definitions/orm.rb | 65 + .../record/step_definitions/validations.rb | 47 + features/record/validations.feature | 208 + features/s3/async_buckets.feature | 55 + features/s3/high_level/bucket_acls.feature | 40 + features/s3/high_level/buckets.feature | 80 + features/s3/high_level/errors.feature | 31 + features/s3/high_level/object_acls.feature | 40 + features/s3/high_level/objects.feature | 164 + features/s3/high_level/policies.feature | 55 + features/s3/high_level/post.feature | 128 + features/s3/high_level/step_definitions.rb | 39 + .../step_definitions/bucket_acls.rb | 67 + .../s3/high_level/step_definitions/buckets.rb | 66 + .../s3/high_level/step_definitions/errors.rb | 44 + .../step_definitions/object_acls.rb | 50 + .../s3/high_level/step_definitions/objects.rb | 220 + .../high_level/step_definitions/policies.rb | 57 + .../s3/high_level/step_definitions/post.rb | 142 + .../s3/high_level/step_definitions/tree.rb | 61 + .../s3/high_level/step_definitions/uploads.rb | 143 + .../s3/high_level/step_definitions/urls.rb | 58 + .../high_level/step_definitions/versions.rb | 74 + features/s3/high_level/tree.feature | 121 + features/s3/high_level/uploads.feature | 173 + features/s3/high_level/urls.feature | 61 + features/s3/high_level/versions.feature | 97 + features/s3/low_level/bucket_acls.feature | 60 + features/s3/low_level/buckets.feature | 72 + features/s3/low_level/object_acls.feature | 60 + features/s3/low_level/policies.feature | 61 + .../s3/low_level/step_definitions/acls.rb | 88 + .../s3/low_level/step_definitions/buckets.rb | 146 + .../low_level/step_definitions/endpoints.rb | 14 + .../low_level/step_definitions/object_acls.rb | 99 + .../s3/low_level/step_definitions/objects.rb | 20 + .../s3/low_level/step_definitions/policies.rb | 109 + .../s3/low_level/step_definitions/uploads.rb | 92 + .../s3/low_level/step_definitions/versions.rb | 28 + features/s3/low_level/uploads.feature | 84 + features/s3/low_level/versions.feature | 38 + features/s3/step_definitions/async_buckets.rb | 28 + features/simple_db/high_level/count.feature | 61 + features/simple_db/high_level/domains.feature | 50 + features/simple_db/high_level/errors.feature | 28 + features/simple_db/high_level/items.feature | 333 + features/simple_db/high_level/select.feature | 105 + .../high_level/step_definitions/count.rb | 34 + .../high_level/step_definitions/domains.rb | 53 + .../high_level/step_definitions/items.rb | 237 + .../high_level/step_definitions/select.rb | 139 + features/simple_db/low_level/domains.feature | 68 + features/simple_db/low_level/items.feature | 63 + .../low_level/step_definitions/domains.rb | 81 + .../low_level/step_definitions/items.rb | 130 + .../email_addresses.feature | 38 + features/simple_email_service/errors.feature | 27 + features/simple_email_service/quotas.feature | 27 + features/simple_email_service/send.feature | 57 + .../simple_email_service/statistics.feature | 28 + .../step_definitions/email_addresses.rb | 24 + .../step_definitions/quotas.rb | 25 + .../step_definitions/send.rb | 42 + .../step_definitions/ses.rb | 25 + .../step_definitions/statistics.rb | 29 + features/sns/errors.feature | 30 + features/sns/step_definitions/sns.rb | 29 + .../sns/step_definitions/subscriptions.rb | 58 + features/sns/step_definitions/topics.rb | 76 + features/sns/subscriptions.feature | 62 + features/sns/topics.feature | 68 + features/sqs/messages.feature | 101 + features/sqs/policies.feature | 28 + features/sqs/queues.feature | 79 + features/sqs/step_definitions/messages.rb | 145 + features/sqs/step_definitions/policies.rb | 25 + features/sqs/step_definitions/queues.rb | 95 + features/sqs/step_definitions/sqs.rb | 31 + features/step_definitions.rb | 216 + features/support/common.rb | 197 + lib/aws-sdk.rb | 14 + lib/aws.rb | 63 + lib/aws/api_config.rb | 45 + README => lib/aws/api_config/.document | 0 lib/aws/api_config/EC2-2011-02-28.yml | 2314 +++ lib/aws/api_config/SNS-2010-03-31.yml | 171 + lib/aws/api_config/SQS-2009-02-01.yml | 161 + lib/aws/api_config/SimpleDB-2009-04-15.yml | 278 + .../SimpleEmailService-2010-12-01.yml | 147 + lib/aws/api_config_transform.rb | 32 + lib/aws/async_handle.rb | 90 + lib/aws/authorize_v2.rb | 37 + lib/aws/authorize_v3.rb | 37 + lib/aws/base_client.rb | 524 + lib/aws/cacheable.rb | 92 + lib/aws/common.rb | 228 + lib/aws/configurable.rb | 36 + lib/aws/configuration.rb | 272 + lib/aws/configured_client_methods.rb | 81 + lib/aws/configured_grammars.rb | 65 + lib/aws/configured_option_grammars.rb | 46 + lib/aws/configured_xml_grammars.rb | 47 + lib/aws/default_signer.rb | 38 + lib/aws/ec2.rb | 321 + lib/aws/ec2/attachment.rb | 149 + lib/aws/ec2/attachment_collection.rb | 57 + lib/aws/ec2/availability_zone.rb | 80 + lib/aws/ec2/availability_zone_collection.rb | 47 + lib/aws/ec2/block_device_mappings.rb | 53 + lib/aws/ec2/client.rb | 54 + lib/aws/ec2/client/xml.rb | 127 + lib/aws/ec2/collection.rb | 39 + lib/aws/ec2/config_transform.rb | 63 + lib/aws/ec2/elastic_ip.rb | 107 + lib/aws/ec2/elastic_ip_collection.rb | 85 + lib/aws/ec2/errors.rb | 29 + lib/aws/ec2/filtered_collection.rb | 65 + lib/aws/ec2/has_permissions.rb | 46 + lib/aws/ec2/image.rb | 245 + lib/aws/ec2/image_collection.rb | 235 + lib/aws/ec2/instance.rb | 515 + lib/aws/ec2/instance_collection.rb | 276 + lib/aws/ec2/key_pair.rb | 86 + lib/aws/ec2/key_pair_collection.rb | 102 + lib/aws/ec2/permission_collection.rb | 177 + lib/aws/ec2/region.rb | 81 + lib/aws/ec2/region_collection.rb | 55 + lib/aws/ec2/request.rb | 27 + lib/aws/ec2/reserved_instances.rb | 50 + lib/aws/ec2/reserved_instances_collection.rb | 44 + lib/aws/ec2/reserved_instances_offering.rb | 55 + .../reserved_instances_offering_collection.rb | 43 + lib/aws/ec2/resource.rb | 340 + lib/aws/ec2/resource_tag_collection.rb | 218 + lib/aws/ec2/security_group.rb | 246 + lib/aws/ec2/security_group/ip_permission.rb | 70 + .../ip_permission_collection.rb | 59 + lib/aws/ec2/security_group_collection.rb | 132 + lib/aws/ec2/snapshot.rb | 138 + lib/aws/ec2/snapshot_collection.rb | 90 + lib/aws/ec2/tag.rb | 88 + lib/aws/ec2/tag_collection.rb | 114 + lib/aws/ec2/tagged_collection.rb | 48 + lib/aws/ec2/tagged_item.rb | 87 + lib/aws/ec2/volume.rb | 190 + lib/aws/ec2/volume_collection.rb | 95 + lib/aws/errors.rb | 129 + lib/aws/http/builtin_handler.rb | 69 + lib/aws/http/curb_handler.rb | 123 + lib/aws/http/handler.rb | 77 + lib/aws/http/httparty_handler.rb | 61 + lib/aws/http/request.rb | 136 + lib/aws/http/request_param.rb | 63 + lib/aws/http/response.rb | 75 + lib/aws/ignore_result_element.rb | 38 + lib/aws/indifferent_hash.rb | 86 + lib/aws/inflection.rb | 46 + lib/aws/lazy_error_classes.rb | 64 + lib/aws/meta_utils.rb | 43 + lib/aws/model.rb | 57 + lib/aws/naming.rb | 32 + lib/aws/option_grammar.rb | 544 + lib/aws/policy.rb | 912 ++ lib/aws/rails.rb | 209 + lib/aws/record.rb | 79 + lib/aws/record/attribute.rb | 94 + lib/aws/record/attribute_macros.rb | 288 + lib/aws/record/attributes/boolean.rb | 49 + lib/aws/record/attributes/datetime.rb | 86 + lib/aws/record/attributes/float.rb | 48 + lib/aws/record/attributes/integer.rb | 68 + lib/aws/record/attributes/sortable_float.rb | 60 + lib/aws/record/attributes/sortable_integer.rb | 95 + lib/aws/record/attributes/string.rb | 69 + lib/aws/record/base.rb | 728 + lib/aws/record/conversion.rb | 38 + lib/aws/record/dirty_tracking.rb | 286 + lib/aws/record/errors.rb | 153 + lib/aws/record/exceptions.rb | 48 + lib/aws/record/finder_methods.rb | 262 + lib/aws/record/naming.rb | 31 + lib/aws/record/scope.rb | 157 + lib/aws/record/validations.rb | 653 + lib/aws/record/validator.rb | 237 + lib/aws/record/validators/acceptance.rb | 51 + lib/aws/record/validators/block.rb | 38 + lib/aws/record/validators/confirmation.rb | 43 + lib/aws/record/validators/count.rb | 108 + lib/aws/record/validators/exclusion.rb | 43 + lib/aws/record/validators/format.rb | 57 + lib/aws/record/validators/inclusion.rb | 56 + lib/aws/record/validators/length.rb | 107 + lib/aws/record/validators/numericality.rb | 138 + lib/aws/record/validators/presence.rb | 45 + lib/aws/resource_cache.rb | 39 + lib/aws/response.rb | 113 + lib/aws/response_cache.rb | 50 + lib/aws/s3.rb | 109 + lib/aws/s3/access_control_list.rb | 252 + lib/aws/s3/acl_object.rb | 266 + lib/aws/s3/bucket.rb | 320 + lib/aws/s3/bucket_collection.rb | 122 + lib/aws/s3/bucket_version_collection.rb | 85 + lib/aws/s3/client.rb | 999 ++ lib/aws/s3/client/xml.rb | 190 + lib/aws/s3/data_options.rb | 99 + lib/aws/s3/errors.rb | 43 + lib/aws/s3/multipart_upload.rb | 318 + lib/aws/s3/multipart_upload_collection.rb | 78 + lib/aws/s3/object_collection.rb | 159 + lib/aws/s3/object_metadata.rb | 67 + lib/aws/s3/object_upload_collection.rb | 83 + lib/aws/s3/object_version.rb | 141 + lib/aws/s3/object_version_collection.rb | 78 + lib/aws/s3/paginated_collection.rb | 94 + lib/aws/s3/policy.rb | 76 + lib/aws/s3/prefix_and_delimiter_collection.rb | 56 + lib/aws/s3/prefixed_collection.rb | 84 + lib/aws/s3/presigned_post.rb | 504 + lib/aws/s3/request.rb | 198 + lib/aws/s3/s3_object.rb | 794 ++ lib/aws/s3/tree.rb | 116 + lib/aws/s3/tree/branch_node.rb | 71 + lib/aws/s3/tree/child_collection.rb | 108 + lib/aws/s3/tree/leaf_node.rb | 99 + lib/aws/s3/tree/node.rb | 22 + lib/aws/s3/tree/parent.rb | 90 + lib/aws/s3/uploaded_part.rb | 82 + lib/aws/s3/uploaded_part_collection.rb | 86 + lib/aws/service_interface.rb | 60 + lib/aws/simple_db.rb | 202 + lib/aws/simple_db/attribute.rb | 159 + lib/aws/simple_db/attribute_collection.rb | 227 + lib/aws/simple_db/client.rb | 52 + lib/aws/simple_db/client/options.rb | 34 + lib/aws/simple_db/client/xml.rb | 68 + lib/aws/simple_db/consistent_read_option.rb | 42 + lib/aws/simple_db/delete_attributes.rb | 64 + lib/aws/simple_db/domain.rb | 118 + lib/aws/simple_db/domain_collection.rb | 116 + lib/aws/simple_db/domain_metadata.rb | 112 + lib/aws/simple_db/errors.rb | 46 + lib/aws/simple_db/expect_condition_option.rb | 45 + lib/aws/simple_db/item.rb | 84 + lib/aws/simple_db/item_collection.rb | 594 + lib/aws/simple_db/item_data.rb | 70 + lib/aws/simple_db/put_attributes.rb | 62 + lib/aws/simple_db/request.rb | 27 + lib/aws/simple_email_service.rb | 373 + lib/aws/simple_email_service/client.rb | 39 + .../simple_email_service/client/options.rb | 24 + lib/aws/simple_email_service/client/xml.rb | 38 + .../email_address_collection.rb | 66 + lib/aws/simple_email_service/errors.rb | 29 + lib/aws/simple_email_service/quotas.rb | 64 + lib/aws/simple_email_service/request.rb | 27 + lib/aws/sns.rb | 69 + lib/aws/sns/client.rb | 37 + lib/aws/sns/client/options.rb | 24 + lib/aws/sns/client/xml.rb | 38 + lib/aws/sns/errors.rb | 29 + lib/aws/sns/policy.rb | 49 + lib/aws/sns/request.rb | 27 + lib/aws/sns/subscription.rb | 100 + lib/aws/sns/subscription_collection.rb | 84 + lib/aws/sns/topic.rb | 384 + lib/aws/sns/topic_collection.rb | 70 + lib/aws/sns/topic_subscription_collection.rb | 58 + lib/aws/sqs.rb | 70 + lib/aws/sqs/client.rb | 38 + lib/aws/sqs/client/xml.rb | 36 + lib/aws/sqs/errors.rb | 33 + lib/aws/sqs/policy.rb | 50 + lib/aws/sqs/queue.rb | 507 + lib/aws/sqs/queue_collection.rb | 105 + lib/aws/sqs/received_message.rb | 184 + lib/aws/sqs/received_sns_message.rb | 112 + lib/aws/sqs/request.rb | 44 + lib/aws/xml_grammar.rb | 923 ++ recipebook/.gitignore | 3 + recipebook/Gemfile | 17 + recipebook/Gemfile.lock | 151 + recipebook/README | 20 + recipebook/Rakefile | 20 + .../app/controllers/application_controller.rb | 26 + .../app/controllers/index_controller.rb | 22 + .../app/controllers/recipes_controller.rb | 61 + recipebook/app/helpers/application_helper.rb | 31 + recipebook/app/helpers/recipes_helper.rb | 15 + recipebook/app/models/recipe.rb | 33 + recipebook/app/views/index/index.rhtml | 6 + .../app/views/layouts/application.html.erb | 14 + .../app/views/layouts/application.html.haml | 23 + recipebook/app/views/recipes/_form.html.haml | 25 + recipebook/app/views/recipes/edit.html.haml | 15 + recipebook/app/views/recipes/index.html.haml | 22 + recipebook/app/views/recipes/new.html.haml | 15 + recipebook/app/views/recipes/show.html.haml | 33 + recipebook/config.ru | 4 + recipebook/config/application.rb | 55 + recipebook/config/boot.rb | 19 + recipebook/config/cucumber.yml | 21 + recipebook/config/database.yml | 38 + recipebook/config/environment.rb | 18 + recipebook/config/environments/development.rb | 38 + recipebook/config/environments/production.rb | 62 + recipebook/config/environments/test.rb | 48 + recipebook/config/initializers/aws.rb | 18 + .../initializers/backtrace_silencers.rb | 20 + recipebook/config/initializers/form_wow.rb | 15 + recipebook/config/initializers/inflections.rb | 23 + recipebook/config/initializers/mime_types.rb | 18 + .../config/initializers/monkey_patch.rb | 19 + .../config/initializers/secret_token.rb | 20 + .../config/initializers/session_store.rb | 21 + recipebook/config/locales/en.yml | 18 + recipebook/config/routes.rb | 76 + recipebook/db/schema.rb | 15 + recipebook/db/seeds.rb | 20 + recipebook/features/recipes/crud.feature | 58 + .../features/recipes/step_definitions/crud.rb | 22 + .../step_definitions/more_web_steps.rb | 33 + .../features/step_definitions/web_steps.rb | 232 + recipebook/features/support/cleanup.rb | 17 + recipebook/features/support/env.rb | 70 + recipebook/features/support/paths.rb | 46 + recipebook/lib/tasks/cucumber.rake | 66 + recipebook/public/404.html | 26 + recipebook/public/422.html | 26 + recipebook/public/500.html | 26 + recipebook/public/favicon.ico | 0 recipebook/public/images/rails.png | Bin 0 -> 6646 bytes recipebook/public/javascripts/application.js | 45 + recipebook/public/javascripts/controls.js | 965 ++ recipebook/public/javascripts/dragdrop.js | 974 ++ recipebook/public/javascripts/effects.js | 1123 ++ recipebook/public/javascripts/jquery-ui.js | 11577 ++++++++++++++++ .../public/javascripts/jquery-ui.min.js | 406 + recipebook/public/javascripts/jquery.js | 8316 +++++++++++ recipebook/public/javascripts/jquery.min.js | 16 + recipebook/public/javascripts/prototype.js | 6001 ++++++++ recipebook/public/javascripts/rails.js | 159 + recipebook/public/robots.txt | 5 + recipebook/public/stylesheets/application.css | 74 + .../public/stylesheets/sass/application.sass | 76 + recipebook/script/cucumber | 10 + recipebook/script/rails | 6 + recipebook/spec/spec_helper.rb | 40 + recipebook/test/performance/browsing_test.rb | 22 + recipebook/test/test_helper.rb | 26 + recipebook/vendor/plugins/form_wow/init.rb | 14 + .../vendor/plugins/form_wow/lib/form_wow.rb | 30 + .../plugins/form_wow/lib/form_wow/builder.rb | 201 + .../plugins/form_wow/lib/form_wow/helpers.rb | 143 + samples/.gitignore | 5 + samples/ec2/run_instance.rb | 84 + samples/s3/upload_file.rb | 43 + samples/samples_config.rb | 43 + spec/aws/async_handle_spec.rb | 271 + spec/aws/cacheable_spec.rb | 275 + spec/aws/configurable_spec.rb | 47 + spec/aws/configuration_spec.rb | 378 + spec/aws/configured_client_methods_spec.rb | 134 + spec/aws/configured_option_grammars_spec.rb | 30 + spec/aws/configured_xml_grammars_spec.rb | 30 + spec/aws/default_signer_spec.rb | 170 + spec/aws/ec2/attachment_collection_spec.rb | 97 + spec/aws/ec2/attachment_spec.rb | 357 + .../ec2/availability_zone_collection_spec.rb | 45 + spec/aws/ec2/availability_zone_spec.rb | 138 + spec/aws/ec2/client/xml_spec.rb | 400 + spec/aws/ec2/client_spec.rb | 206 + spec/aws/ec2/elastic_ip_collection_spec.rb | 95 + spec/aws/ec2/elastic_ip_spec.rb | 113 + spec/aws/ec2/image_collection_spec.rb | 349 + spec/aws/ec2/image_spec.rb | 667 + spec/aws/ec2/instance_collection_spec.rb | 449 + spec/aws/ec2/instance_spec.rb | 1324 ++ spec/aws/ec2/key_pair_collection_spec.rb | 121 + spec/aws/ec2/key_pair_spec.rb | 145 + spec/aws/ec2/permission_collection_spec.rb | 186 + spec/aws/ec2/region_collection_spec.rb | 56 + spec/aws/ec2/region_spec.rb | 166 + spec/aws/ec2/request_spec.rb | 26 + .../ec2/reserved_instances_collection_spec.rb | 45 + ...rved_instances_offering_collection_spec.rb | 45 + .../ec2/reserved_instances_offering_spec.rb | 99 + spec/aws/ec2/reserved_instances_spec.rb | 74 + spec/aws/ec2/resource_tag_collection_spec.rb | 493 + .../ip_permission_collection_spec.rb | 101 + .../ec2/security_group/ip_permission_spec.rb | 147 + .../aws/ec2/security_group_collection_spec.rb | 84 + spec/aws/ec2/security_group_spec.rb | 308 + spec/aws/ec2/snapshot_collection_spec.rb | 87 + spec/aws/ec2/snapshot_spec.rb | 257 + spec/aws/ec2/tag_collection_spec.rb | 154 + spec/aws/ec2/tag_spec.rb | 194 + spec/aws/ec2/volume_collection_spec.rb | 87 + spec/aws/ec2/volume_spec.rb | 316 + spec/aws/ec2_spec.rb | 86 + spec/aws/errors_spec.rb | 69 + spec/aws/http/builtin_handler_spec.rb | 126 + spec/aws/http/curb_handler_spec.rb | 108 + spec/aws/http/handler_spec.rb | 233 + spec/aws/http/httparty_handler_spec.rb | 25 + spec/aws/http/request_param_spec.rb | 103 + spec/aws/http/request_spec.rb | 217 + spec/aws/http/response_spec.rb | 119 + spec/aws/indifferent_hash_spec.rb | 127 + spec/aws/inflection_spec.rb | 97 + spec/aws/lazy_error_classes_spec.rb | 53 + spec/aws/meta_utils_spec.rb | 38 + spec/aws/option_grammar_spec.rb | 437 + spec/aws/policy_spec.rb | 606 + spec/aws/rails_spec.rb | 137 + spec/aws/record/attributes_spec.rb | 528 + .../record/base/attribute_deletion_spec.rb | 94 + spec/aws/record/base/class_methods_spec.rb | 780 ++ spec/aws/record/base/dirty_tracking_spec.rb | 291 + spec/aws/record/base/empty_records_spec.rb | 87 + spec/aws/record/base/errors_spec.rb | 219 + spec/aws/record/base/instance_methods_spec.rb | 402 + .../record/base/optimistic_locking_spec.rb | 117 + spec/aws/record/base/timestamps_spec.rb | 97 + .../base/validations/acceptance_spec.rb | 129 + .../base/validations/confirmation_spec.rb | 104 + .../aws/record/base/validations/count_spec.rb | 314 + .../record/base/validations/exclusion_spec.rb | 121 + .../record/base/validations/format_spec.rb | 154 + .../record/base/validations/inclusion_spec.rb | 138 + .../record/base/validations/length_spec.rb | 273 + .../base/validations/numericality_spec.rb | 671 + .../record/base/validations/presence_spec.rb | 63 + spec/aws/record/scope_spec.rb | 501 + spec/aws/record_spec.rb | 33 + spec/aws/resource_cache_spec.rb | 74 + spec/aws/response_cache_spec.rb | 143 + spec/aws/response_spec.rb | 185 + spec/aws/s3/access_control_list_spec.rb | 843 ++ spec/aws/s3/bucket_collection_spec.rb | 136 + spec/aws/s3/bucket_spec.rb | 462 + spec/aws/s3/bucket_version_collection_spec.rb | 195 + spec/aws/s3/client/xml_spec.rb | 722 + spec/aws/s3/client_spec.rb | 1687 +++ spec/aws/s3/errors_spec.rb | 71 + .../s3/multipart_upload_collection_spec.rb | 106 + spec/aws/s3/multipart_upload_spec.rb | 561 + spec/aws/s3/object_collection_spec.rb | 152 + spec/aws/s3/object_metadata_spec.rb | 111 + spec/aws/s3/object_upload_collection_spec.rb | 191 + spec/aws/s3/object_version_collection_spec.rb | 98 + spec/aws/s3/object_version_spec.rb | 178 + spec/aws/s3/policy_spec.rb | 64 + spec/aws/s3/presigned_post_spec.rb | 542 + spec/aws/s3/request_spec.rb | 285 + spec/aws/s3/s3_object/copy_spec.rb | 173 + spec/aws/s3/s3_object_spec.rb | 831 ++ spec/aws/s3/tree/branch_node_spec.rb | 141 + spec/aws/s3/tree/child_collection_spec.rb | 158 + spec/aws/s3/tree/leaf_node_spec.rb | 160 + spec/aws/s3/tree/node_spec.rb | 23 + spec/aws/s3/tree_spec.rb | 114 + spec/aws/s3/uploaded_part_collection_spec.rb | 118 + spec/aws/s3/uploaded_part_spec.rb | 151 + spec/aws/s3_spec.rb | 52 + .../simple_db/attribute_collection_spec.rb | 478 + spec/aws/simple_db/attribute_spec.rb | 310 + spec/aws/simple_db/client/xml_spec.rb | 214 + spec/aws/simple_db/client_spec.rb | 332 + spec/aws/simple_db/domain_collection_spec.rb | 137 + spec/aws/simple_db/domain_metadata_spec.rb | 94 + spec/aws/simple_db/domain_spec.rb | 176 + spec/aws/simple_db/errors_spec.rb | 63 + spec/aws/simple_db/item_collection_spec.rb | 703 + spec/aws/simple_db/item_data_spec.rb | 91 + spec/aws/simple_db/item_spec.rb | 152 + spec/aws/simple_db/request_spec.rb | 26 + spec/aws/simple_db_spec.rb | 92 + .../simple_email_service/client/xml_spec.rb | 44 + spec/aws/simple_email_service/client_spec.rb | 58 + .../email_address_collection_spec.rb | 94 + spec/aws/simple_email_service/quotas_spec.rb | 91 + spec/aws/simple_email_service/request_spec.rb | 26 + spec/aws/simple_email_service_spec.rb | 234 + spec/aws/sns/policy_spec.rb | 38 + spec/aws/sns/request_spec.rb | 26 + spec/aws/sns/subscription_collection_spec.rb | 95 + spec/aws/sns/subscription_spec.rb | 108 + spec/aws/sns/topic_collection_spec.rb | 150 + spec/aws/sns/topic_spec.rb | 507 + .../sns/topic_subscription_collection_spec.rb | 115 + spec/aws/sns_spec.rb | 52 + spec/aws/sqs/client_spec.rb | 62 + spec/aws/sqs/policy_spec.rb | 38 + spec/aws/sqs/queue_collection_spec.rb | 113 + spec/aws/sqs/queue_spec.rb | 792 ++ spec/aws/sqs/received_message_spec.rb | 171 + spec/aws/sqs/received_sns_message_spec.rb | 109 + spec/aws/sqs/request_spec.rb | 62 + spec/aws/sqs_spec.rb | 46 + spec/aws/xml_grammar_spec.rb | 1219 ++ spec/aws_spec.rb | 189 + spec/shared/accepts_configuration_examples.rb | 32 + spec/shared/aws_client_examples.rb | 627 + spec/shared/aws_record_examples.rb | 75 + spec/shared/blocking_handler_examples.rb | 71 + spec/shared/class_methods_module_examples.rb | 34 + spec/shared/collection_examples.rb | 61 + .../configured_grammars_module_examples.rb | 154 + .../ec2/collection_array_access_examples.rb | 35 + spec/shared/ec2/collection_examples.rb | 78 + .../ec2/describe_call_attribute_examples.rb | 61 + spec/shared/ec2/exists_method_examples.rb | 57 + .../ec2/fitlered_collection_examples.rb | 63 + spec/shared/ec2/model_object_examples.rb | 52 + .../ec2/public_private_accessor_examples.rb | 101 + .../reserved_instances_attributes_examples.rb | 86 + spec/shared/ec2/resource_accessor_examples.rb | 42 + spec/shared/ec2/resource_examples.rb | 84 + spec/shared/ec2/tagged_collection_examples.rb | 98 + spec/shared/ec2/tagged_item_examples.rb | 130 + spec/shared/enumerable_examples.rb | 26 + spec/shared/indifferent_access_examples.rb | 97 + spec/shared/pagination_examples.rb | 36 + spec/shared/policy_examples.rb | 314 + spec/shared/record/validation_examples.rb | 183 + spec/shared/request_examples.rb | 124 + spec/shared/resource_examples.rb | 76 + spec/shared/s3/acl_examples.rb | 87 + spec/shared/s3/model_object_examples.rb | 60 + .../s3/paginated_collection_examples.rb | 116 + .../shared/s3/prefixed_collection_examples.rb | 71 + spec/shared/s3/tree_like_examples.rb | 90 + ...accepts_consistent_read_option_examples.rb | 63 + .../simple_db/expect_condition_examples.rb | 54 + .../shared/simple_db/model_object_examples.rb | 52 + spec/shared/sqs/model_object_examples.rb | 60 + spec/shared/stub_config_examples.rb | 20 + spec/shared/uses_cached_responses_examples.rb | 76 + spec/spec_helper.rb | 23 + tasks/cucumber.rake | 16 + tasks/docs.rake | 30 + tasks/flog.rake | 48 + tasks/gems.rake | 49 + tasks/rcov.rake | 18 + tasks/rspec.rake | 16 + tasks/version.rb | 14 + 597 files changed, 108390 insertions(+) create mode 100644 .yardopts create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 LICENSE.txt create mode 100644 NOTICE.txt create mode 100644 README.rdoc create mode 100644 Rakefile create mode 100644 features/ec2/combined.feature create mode 100644 features/ec2/elastic_ips.feature create mode 100644 features/ec2/errors.feature create mode 100644 features/ec2/image_attributes.feature create mode 100644 features/ec2/images.feature create mode 100644 features/ec2/instance_attributes.feature create mode 100644 features/ec2/instances.feature create mode 100644 features/ec2/key_pairs.feature create mode 100644 features/ec2/regions.feature create mode 100644 features/ec2/reserved_instances.feature create mode 100644 features/ec2/security_groups.feature create mode 100644 features/ec2/snapshot_attributes.feature create mode 100644 features/ec2/snapshots.feature create mode 100644 features/ec2/step_definitions/combined.rb create mode 100644 features/ec2/step_definitions/ec2.rb create mode 100644 features/ec2/step_definitions/elasic_ips.rb create mode 100644 features/ec2/step_definitions/errors.rb create mode 100644 features/ec2/step_definitions/image_attributes.rb create mode 100644 features/ec2/step_definitions/images.rb create mode 100644 features/ec2/step_definitions/instance_attributes.rb create mode 100644 features/ec2/step_definitions/instances.rb create mode 100644 features/ec2/step_definitions/key_pairs.rb create mode 100644 features/ec2/step_definitions/regions.rb create mode 100644 features/ec2/step_definitions/reserved_instances.rb create mode 100644 features/ec2/step_definitions/security_groups.rb create mode 100644 features/ec2/step_definitions/snapshot_attributes.rb create mode 100644 features/ec2/step_definitions/snapshots.rb create mode 100644 features/ec2/step_definitions/tagging.rb create mode 100644 features/ec2/step_definitions/volume_attributes.rb create mode 100644 features/ec2/step_definitions/volumes.rb create mode 100644 features/ec2/step_definitions/zones.rb create mode 100644 features/ec2/tagging.feature create mode 100644 features/ec2/volume_attributes.feature create mode 100644 features/ec2/volumes.feature create mode 100644 features/ec2/zones.feature create mode 100644 features/record/count.feature create mode 100644 features/record/dirty_tracking.feature create mode 100644 features/record/find.feature create mode 100644 features/record/optimistic_locking.feature create mode 100644 features/record/scope.feature create mode 100644 features/record/step_definitions/count.rb create mode 100644 features/record/step_definitions/dirty_tracking.rb create mode 100644 features/record/step_definitions/optimistic_locking.rb create mode 100644 features/record/step_definitions/orm.rb create mode 100644 features/record/step_definitions/validations.rb create mode 100644 features/record/validations.feature create mode 100644 features/s3/async_buckets.feature create mode 100644 features/s3/high_level/bucket_acls.feature create mode 100644 features/s3/high_level/buckets.feature create mode 100644 features/s3/high_level/errors.feature create mode 100644 features/s3/high_level/object_acls.feature create mode 100644 features/s3/high_level/objects.feature create mode 100644 features/s3/high_level/policies.feature create mode 100644 features/s3/high_level/post.feature create mode 100644 features/s3/high_level/step_definitions.rb create mode 100644 features/s3/high_level/step_definitions/bucket_acls.rb create mode 100644 features/s3/high_level/step_definitions/buckets.rb create mode 100644 features/s3/high_level/step_definitions/errors.rb create mode 100644 features/s3/high_level/step_definitions/object_acls.rb create mode 100644 features/s3/high_level/step_definitions/objects.rb create mode 100644 features/s3/high_level/step_definitions/policies.rb create mode 100644 features/s3/high_level/step_definitions/post.rb create mode 100644 features/s3/high_level/step_definitions/tree.rb create mode 100644 features/s3/high_level/step_definitions/uploads.rb create mode 100644 features/s3/high_level/step_definitions/urls.rb create mode 100644 features/s3/high_level/step_definitions/versions.rb create mode 100644 features/s3/high_level/tree.feature create mode 100644 features/s3/high_level/uploads.feature create mode 100644 features/s3/high_level/urls.feature create mode 100644 features/s3/high_level/versions.feature create mode 100644 features/s3/low_level/bucket_acls.feature create mode 100644 features/s3/low_level/buckets.feature create mode 100644 features/s3/low_level/object_acls.feature create mode 100644 features/s3/low_level/policies.feature create mode 100644 features/s3/low_level/step_definitions/acls.rb create mode 100644 features/s3/low_level/step_definitions/buckets.rb create mode 100644 features/s3/low_level/step_definitions/endpoints.rb create mode 100644 features/s3/low_level/step_definitions/object_acls.rb create mode 100644 features/s3/low_level/step_definitions/objects.rb create mode 100644 features/s3/low_level/step_definitions/policies.rb create mode 100644 features/s3/low_level/step_definitions/uploads.rb create mode 100644 features/s3/low_level/step_definitions/versions.rb create mode 100644 features/s3/low_level/uploads.feature create mode 100644 features/s3/low_level/versions.feature create mode 100644 features/s3/step_definitions/async_buckets.rb create mode 100644 features/simple_db/high_level/count.feature create mode 100644 features/simple_db/high_level/domains.feature create mode 100644 features/simple_db/high_level/errors.feature create mode 100644 features/simple_db/high_level/items.feature create mode 100644 features/simple_db/high_level/select.feature create mode 100644 features/simple_db/high_level/step_definitions/count.rb create mode 100644 features/simple_db/high_level/step_definitions/domains.rb create mode 100644 features/simple_db/high_level/step_definitions/items.rb create mode 100644 features/simple_db/high_level/step_definitions/select.rb create mode 100644 features/simple_db/low_level/domains.feature create mode 100644 features/simple_db/low_level/items.feature create mode 100644 features/simple_db/low_level/step_definitions/domains.rb create mode 100644 features/simple_db/low_level/step_definitions/items.rb create mode 100644 features/simple_email_service/email_addresses.feature create mode 100644 features/simple_email_service/errors.feature create mode 100644 features/simple_email_service/quotas.feature create mode 100644 features/simple_email_service/send.feature create mode 100644 features/simple_email_service/statistics.feature create mode 100644 features/simple_email_service/step_definitions/email_addresses.rb create mode 100644 features/simple_email_service/step_definitions/quotas.rb create mode 100644 features/simple_email_service/step_definitions/send.rb create mode 100644 features/simple_email_service/step_definitions/ses.rb create mode 100644 features/simple_email_service/step_definitions/statistics.rb create mode 100644 features/sns/errors.feature create mode 100644 features/sns/step_definitions/sns.rb create mode 100644 features/sns/step_definitions/subscriptions.rb create mode 100644 features/sns/step_definitions/topics.rb create mode 100644 features/sns/subscriptions.feature create mode 100644 features/sns/topics.feature create mode 100644 features/sqs/messages.feature create mode 100644 features/sqs/policies.feature create mode 100644 features/sqs/queues.feature create mode 100644 features/sqs/step_definitions/messages.rb create mode 100644 features/sqs/step_definitions/policies.rb create mode 100644 features/sqs/step_definitions/queues.rb create mode 100644 features/sqs/step_definitions/sqs.rb create mode 100644 features/step_definitions.rb create mode 100644 features/support/common.rb create mode 100644 lib/aws-sdk.rb create mode 100644 lib/aws.rb create mode 100644 lib/aws/api_config.rb rename README => lib/aws/api_config/.document (100%) create mode 100644 lib/aws/api_config/EC2-2011-02-28.yml create mode 100644 lib/aws/api_config/SNS-2010-03-31.yml create mode 100644 lib/aws/api_config/SQS-2009-02-01.yml create mode 100644 lib/aws/api_config/SimpleDB-2009-04-15.yml create mode 100644 lib/aws/api_config/SimpleEmailService-2010-12-01.yml create mode 100644 lib/aws/api_config_transform.rb create mode 100644 lib/aws/async_handle.rb create mode 100644 lib/aws/authorize_v2.rb create mode 100644 lib/aws/authorize_v3.rb create mode 100644 lib/aws/base_client.rb create mode 100644 lib/aws/cacheable.rb create mode 100644 lib/aws/common.rb create mode 100644 lib/aws/configurable.rb create mode 100644 lib/aws/configuration.rb create mode 100644 lib/aws/configured_client_methods.rb create mode 100644 lib/aws/configured_grammars.rb create mode 100644 lib/aws/configured_option_grammars.rb create mode 100644 lib/aws/configured_xml_grammars.rb create mode 100644 lib/aws/default_signer.rb create mode 100644 lib/aws/ec2.rb create mode 100644 lib/aws/ec2/attachment.rb create mode 100644 lib/aws/ec2/attachment_collection.rb create mode 100644 lib/aws/ec2/availability_zone.rb create mode 100644 lib/aws/ec2/availability_zone_collection.rb create mode 100644 lib/aws/ec2/block_device_mappings.rb create mode 100644 lib/aws/ec2/client.rb create mode 100644 lib/aws/ec2/client/xml.rb create mode 100644 lib/aws/ec2/collection.rb create mode 100644 lib/aws/ec2/config_transform.rb create mode 100644 lib/aws/ec2/elastic_ip.rb create mode 100644 lib/aws/ec2/elastic_ip_collection.rb create mode 100644 lib/aws/ec2/errors.rb create mode 100644 lib/aws/ec2/filtered_collection.rb create mode 100644 lib/aws/ec2/has_permissions.rb create mode 100644 lib/aws/ec2/image.rb create mode 100644 lib/aws/ec2/image_collection.rb create mode 100644 lib/aws/ec2/instance.rb create mode 100644 lib/aws/ec2/instance_collection.rb create mode 100644 lib/aws/ec2/key_pair.rb create mode 100644 lib/aws/ec2/key_pair_collection.rb create mode 100644 lib/aws/ec2/permission_collection.rb create mode 100644 lib/aws/ec2/region.rb create mode 100644 lib/aws/ec2/region_collection.rb create mode 100644 lib/aws/ec2/request.rb create mode 100644 lib/aws/ec2/reserved_instances.rb create mode 100644 lib/aws/ec2/reserved_instances_collection.rb create mode 100644 lib/aws/ec2/reserved_instances_offering.rb create mode 100644 lib/aws/ec2/reserved_instances_offering_collection.rb create mode 100644 lib/aws/ec2/resource.rb create mode 100644 lib/aws/ec2/resource_tag_collection.rb create mode 100644 lib/aws/ec2/security_group.rb create mode 100644 lib/aws/ec2/security_group/ip_permission.rb create mode 100644 lib/aws/ec2/security_group/ip_permission_collection.rb create mode 100644 lib/aws/ec2/security_group_collection.rb create mode 100644 lib/aws/ec2/snapshot.rb create mode 100644 lib/aws/ec2/snapshot_collection.rb create mode 100644 lib/aws/ec2/tag.rb create mode 100644 lib/aws/ec2/tag_collection.rb create mode 100644 lib/aws/ec2/tagged_collection.rb create mode 100644 lib/aws/ec2/tagged_item.rb create mode 100644 lib/aws/ec2/volume.rb create mode 100644 lib/aws/ec2/volume_collection.rb create mode 100644 lib/aws/errors.rb create mode 100644 lib/aws/http/builtin_handler.rb create mode 100644 lib/aws/http/curb_handler.rb create mode 100644 lib/aws/http/handler.rb create mode 100644 lib/aws/http/httparty_handler.rb create mode 100644 lib/aws/http/request.rb create mode 100644 lib/aws/http/request_param.rb create mode 100644 lib/aws/http/response.rb create mode 100644 lib/aws/ignore_result_element.rb create mode 100644 lib/aws/indifferent_hash.rb create mode 100644 lib/aws/inflection.rb create mode 100644 lib/aws/lazy_error_classes.rb create mode 100644 lib/aws/meta_utils.rb create mode 100644 lib/aws/model.rb create mode 100644 lib/aws/naming.rb create mode 100644 lib/aws/option_grammar.rb create mode 100644 lib/aws/policy.rb create mode 100644 lib/aws/rails.rb create mode 100644 lib/aws/record.rb create mode 100644 lib/aws/record/attribute.rb create mode 100644 lib/aws/record/attribute_macros.rb create mode 100644 lib/aws/record/attributes/boolean.rb create mode 100644 lib/aws/record/attributes/datetime.rb create mode 100644 lib/aws/record/attributes/float.rb create mode 100644 lib/aws/record/attributes/integer.rb create mode 100644 lib/aws/record/attributes/sortable_float.rb create mode 100644 lib/aws/record/attributes/sortable_integer.rb create mode 100644 lib/aws/record/attributes/string.rb create mode 100644 lib/aws/record/base.rb create mode 100644 lib/aws/record/conversion.rb create mode 100644 lib/aws/record/dirty_tracking.rb create mode 100644 lib/aws/record/errors.rb create mode 100644 lib/aws/record/exceptions.rb create mode 100644 lib/aws/record/finder_methods.rb create mode 100644 lib/aws/record/naming.rb create mode 100644 lib/aws/record/scope.rb create mode 100644 lib/aws/record/validations.rb create mode 100644 lib/aws/record/validator.rb create mode 100644 lib/aws/record/validators/acceptance.rb create mode 100644 lib/aws/record/validators/block.rb create mode 100644 lib/aws/record/validators/confirmation.rb create mode 100644 lib/aws/record/validators/count.rb create mode 100644 lib/aws/record/validators/exclusion.rb create mode 100644 lib/aws/record/validators/format.rb create mode 100644 lib/aws/record/validators/inclusion.rb create mode 100644 lib/aws/record/validators/length.rb create mode 100644 lib/aws/record/validators/numericality.rb create mode 100644 lib/aws/record/validators/presence.rb create mode 100644 lib/aws/resource_cache.rb create mode 100644 lib/aws/response.rb create mode 100644 lib/aws/response_cache.rb create mode 100644 lib/aws/s3.rb create mode 100644 lib/aws/s3/access_control_list.rb create mode 100644 lib/aws/s3/acl_object.rb create mode 100644 lib/aws/s3/bucket.rb create mode 100644 lib/aws/s3/bucket_collection.rb create mode 100644 lib/aws/s3/bucket_version_collection.rb create mode 100644 lib/aws/s3/client.rb create mode 100644 lib/aws/s3/client/xml.rb create mode 100644 lib/aws/s3/data_options.rb create mode 100644 lib/aws/s3/errors.rb create mode 100644 lib/aws/s3/multipart_upload.rb create mode 100644 lib/aws/s3/multipart_upload_collection.rb create mode 100644 lib/aws/s3/object_collection.rb create mode 100644 lib/aws/s3/object_metadata.rb create mode 100644 lib/aws/s3/object_upload_collection.rb create mode 100644 lib/aws/s3/object_version.rb create mode 100644 lib/aws/s3/object_version_collection.rb create mode 100644 lib/aws/s3/paginated_collection.rb create mode 100644 lib/aws/s3/policy.rb create mode 100644 lib/aws/s3/prefix_and_delimiter_collection.rb create mode 100644 lib/aws/s3/prefixed_collection.rb create mode 100644 lib/aws/s3/presigned_post.rb create mode 100644 lib/aws/s3/request.rb create mode 100644 lib/aws/s3/s3_object.rb create mode 100644 lib/aws/s3/tree.rb create mode 100644 lib/aws/s3/tree/branch_node.rb create mode 100644 lib/aws/s3/tree/child_collection.rb create mode 100644 lib/aws/s3/tree/leaf_node.rb create mode 100644 lib/aws/s3/tree/node.rb create mode 100644 lib/aws/s3/tree/parent.rb create mode 100644 lib/aws/s3/uploaded_part.rb create mode 100644 lib/aws/s3/uploaded_part_collection.rb create mode 100644 lib/aws/service_interface.rb create mode 100644 lib/aws/simple_db.rb create mode 100644 lib/aws/simple_db/attribute.rb create mode 100644 lib/aws/simple_db/attribute_collection.rb create mode 100644 lib/aws/simple_db/client.rb create mode 100644 lib/aws/simple_db/client/options.rb create mode 100644 lib/aws/simple_db/client/xml.rb create mode 100644 lib/aws/simple_db/consistent_read_option.rb create mode 100644 lib/aws/simple_db/delete_attributes.rb create mode 100644 lib/aws/simple_db/domain.rb create mode 100644 lib/aws/simple_db/domain_collection.rb create mode 100644 lib/aws/simple_db/domain_metadata.rb create mode 100644 lib/aws/simple_db/errors.rb create mode 100644 lib/aws/simple_db/expect_condition_option.rb create mode 100644 lib/aws/simple_db/item.rb create mode 100644 lib/aws/simple_db/item_collection.rb create mode 100644 lib/aws/simple_db/item_data.rb create mode 100644 lib/aws/simple_db/put_attributes.rb create mode 100644 lib/aws/simple_db/request.rb create mode 100644 lib/aws/simple_email_service.rb create mode 100644 lib/aws/simple_email_service/client.rb create mode 100644 lib/aws/simple_email_service/client/options.rb create mode 100644 lib/aws/simple_email_service/client/xml.rb create mode 100644 lib/aws/simple_email_service/email_address_collection.rb create mode 100644 lib/aws/simple_email_service/errors.rb create mode 100644 lib/aws/simple_email_service/quotas.rb create mode 100644 lib/aws/simple_email_service/request.rb create mode 100644 lib/aws/sns.rb create mode 100644 lib/aws/sns/client.rb create mode 100644 lib/aws/sns/client/options.rb create mode 100644 lib/aws/sns/client/xml.rb create mode 100644 lib/aws/sns/errors.rb create mode 100644 lib/aws/sns/policy.rb create mode 100644 lib/aws/sns/request.rb create mode 100644 lib/aws/sns/subscription.rb create mode 100644 lib/aws/sns/subscription_collection.rb create mode 100644 lib/aws/sns/topic.rb create mode 100644 lib/aws/sns/topic_collection.rb create mode 100644 lib/aws/sns/topic_subscription_collection.rb create mode 100644 lib/aws/sqs.rb create mode 100644 lib/aws/sqs/client.rb create mode 100644 lib/aws/sqs/client/xml.rb create mode 100644 lib/aws/sqs/errors.rb create mode 100644 lib/aws/sqs/policy.rb create mode 100644 lib/aws/sqs/queue.rb create mode 100644 lib/aws/sqs/queue_collection.rb create mode 100644 lib/aws/sqs/received_message.rb create mode 100644 lib/aws/sqs/received_sns_message.rb create mode 100644 lib/aws/sqs/request.rb create mode 100644 lib/aws/xml_grammar.rb create mode 100644 recipebook/.gitignore create mode 100644 recipebook/Gemfile create mode 100644 recipebook/Gemfile.lock create mode 100644 recipebook/README create mode 100644 recipebook/Rakefile create mode 100644 recipebook/app/controllers/application_controller.rb create mode 100644 recipebook/app/controllers/index_controller.rb create mode 100644 recipebook/app/controllers/recipes_controller.rb create mode 100644 recipebook/app/helpers/application_helper.rb create mode 100644 recipebook/app/helpers/recipes_helper.rb create mode 100644 recipebook/app/models/recipe.rb create mode 100644 recipebook/app/views/index/index.rhtml create mode 100644 recipebook/app/views/layouts/application.html.erb create mode 100644 recipebook/app/views/layouts/application.html.haml create mode 100644 recipebook/app/views/recipes/_form.html.haml create mode 100644 recipebook/app/views/recipes/edit.html.haml create mode 100644 recipebook/app/views/recipes/index.html.haml create mode 100644 recipebook/app/views/recipes/new.html.haml create mode 100644 recipebook/app/views/recipes/show.html.haml create mode 100644 recipebook/config.ru create mode 100644 recipebook/config/application.rb create mode 100644 recipebook/config/boot.rb create mode 100644 recipebook/config/cucumber.yml create mode 100644 recipebook/config/database.yml create mode 100644 recipebook/config/environment.rb create mode 100644 recipebook/config/environments/development.rb create mode 100644 recipebook/config/environments/production.rb create mode 100644 recipebook/config/environments/test.rb create mode 100644 recipebook/config/initializers/aws.rb create mode 100644 recipebook/config/initializers/backtrace_silencers.rb create mode 100644 recipebook/config/initializers/form_wow.rb create mode 100644 recipebook/config/initializers/inflections.rb create mode 100644 recipebook/config/initializers/mime_types.rb create mode 100644 recipebook/config/initializers/monkey_patch.rb create mode 100644 recipebook/config/initializers/secret_token.rb create mode 100644 recipebook/config/initializers/session_store.rb create mode 100644 recipebook/config/locales/en.yml create mode 100644 recipebook/config/routes.rb create mode 100644 recipebook/db/schema.rb create mode 100644 recipebook/db/seeds.rb create mode 100644 recipebook/features/recipes/crud.feature create mode 100644 recipebook/features/recipes/step_definitions/crud.rb create mode 100644 recipebook/features/step_definitions/more_web_steps.rb create mode 100644 recipebook/features/step_definitions/web_steps.rb create mode 100644 recipebook/features/support/cleanup.rb create mode 100644 recipebook/features/support/env.rb create mode 100644 recipebook/features/support/paths.rb create mode 100644 recipebook/lib/tasks/cucumber.rake create mode 100644 recipebook/public/404.html create mode 100644 recipebook/public/422.html create mode 100644 recipebook/public/500.html create mode 100644 recipebook/public/favicon.ico create mode 100644 recipebook/public/images/rails.png create mode 100644 recipebook/public/javascripts/application.js create mode 100644 recipebook/public/javascripts/controls.js create mode 100644 recipebook/public/javascripts/dragdrop.js create mode 100644 recipebook/public/javascripts/effects.js create mode 100644 recipebook/public/javascripts/jquery-ui.js create mode 100644 recipebook/public/javascripts/jquery-ui.min.js create mode 100644 recipebook/public/javascripts/jquery.js create mode 100644 recipebook/public/javascripts/jquery.min.js create mode 100644 recipebook/public/javascripts/prototype.js create mode 100644 recipebook/public/javascripts/rails.js create mode 100644 recipebook/public/robots.txt create mode 100644 recipebook/public/stylesheets/application.css create mode 100644 recipebook/public/stylesheets/sass/application.sass create mode 100755 recipebook/script/cucumber create mode 100755 recipebook/script/rails create mode 100644 recipebook/spec/spec_helper.rb create mode 100644 recipebook/test/performance/browsing_test.rb create mode 100644 recipebook/test/test_helper.rb create mode 100644 recipebook/vendor/plugins/form_wow/init.rb create mode 100644 recipebook/vendor/plugins/form_wow/lib/form_wow.rb create mode 100644 recipebook/vendor/plugins/form_wow/lib/form_wow/builder.rb create mode 100644 recipebook/vendor/plugins/form_wow/lib/form_wow/helpers.rb create mode 100644 samples/.gitignore create mode 100644 samples/ec2/run_instance.rb create mode 100644 samples/s3/upload_file.rb create mode 100644 samples/samples_config.rb create mode 100644 spec/aws/async_handle_spec.rb create mode 100644 spec/aws/cacheable_spec.rb create mode 100644 spec/aws/configurable_spec.rb create mode 100644 spec/aws/configuration_spec.rb create mode 100644 spec/aws/configured_client_methods_spec.rb create mode 100644 spec/aws/configured_option_grammars_spec.rb create mode 100644 spec/aws/configured_xml_grammars_spec.rb create mode 100644 spec/aws/default_signer_spec.rb create mode 100644 spec/aws/ec2/attachment_collection_spec.rb create mode 100644 spec/aws/ec2/attachment_spec.rb create mode 100644 spec/aws/ec2/availability_zone_collection_spec.rb create mode 100644 spec/aws/ec2/availability_zone_spec.rb create mode 100644 spec/aws/ec2/client/xml_spec.rb create mode 100644 spec/aws/ec2/client_spec.rb create mode 100644 spec/aws/ec2/elastic_ip_collection_spec.rb create mode 100644 spec/aws/ec2/elastic_ip_spec.rb create mode 100644 spec/aws/ec2/image_collection_spec.rb create mode 100644 spec/aws/ec2/image_spec.rb create mode 100644 spec/aws/ec2/instance_collection_spec.rb create mode 100644 spec/aws/ec2/instance_spec.rb create mode 100644 spec/aws/ec2/key_pair_collection_spec.rb create mode 100644 spec/aws/ec2/key_pair_spec.rb create mode 100644 spec/aws/ec2/permission_collection_spec.rb create mode 100644 spec/aws/ec2/region_collection_spec.rb create mode 100644 spec/aws/ec2/region_spec.rb create mode 100644 spec/aws/ec2/request_spec.rb create mode 100644 spec/aws/ec2/reserved_instances_collection_spec.rb create mode 100644 spec/aws/ec2/reserved_instances_offering_collection_spec.rb create mode 100644 spec/aws/ec2/reserved_instances_offering_spec.rb create mode 100644 spec/aws/ec2/reserved_instances_spec.rb create mode 100644 spec/aws/ec2/resource_tag_collection_spec.rb create mode 100644 spec/aws/ec2/security_group/ip_permission_collection_spec.rb create mode 100644 spec/aws/ec2/security_group/ip_permission_spec.rb create mode 100644 spec/aws/ec2/security_group_collection_spec.rb create mode 100644 spec/aws/ec2/security_group_spec.rb create mode 100644 spec/aws/ec2/snapshot_collection_spec.rb create mode 100644 spec/aws/ec2/snapshot_spec.rb create mode 100644 spec/aws/ec2/tag_collection_spec.rb create mode 100644 spec/aws/ec2/tag_spec.rb create mode 100644 spec/aws/ec2/volume_collection_spec.rb create mode 100644 spec/aws/ec2/volume_spec.rb create mode 100644 spec/aws/ec2_spec.rb create mode 100644 spec/aws/errors_spec.rb create mode 100644 spec/aws/http/builtin_handler_spec.rb create mode 100644 spec/aws/http/curb_handler_spec.rb create mode 100644 spec/aws/http/handler_spec.rb create mode 100644 spec/aws/http/httparty_handler_spec.rb create mode 100644 spec/aws/http/request_param_spec.rb create mode 100644 spec/aws/http/request_spec.rb create mode 100644 spec/aws/http/response_spec.rb create mode 100644 spec/aws/indifferent_hash_spec.rb create mode 100644 spec/aws/inflection_spec.rb create mode 100644 spec/aws/lazy_error_classes_spec.rb create mode 100644 spec/aws/meta_utils_spec.rb create mode 100644 spec/aws/option_grammar_spec.rb create mode 100644 spec/aws/policy_spec.rb create mode 100644 spec/aws/rails_spec.rb create mode 100644 spec/aws/record/attributes_spec.rb create mode 100644 spec/aws/record/base/attribute_deletion_spec.rb create mode 100644 spec/aws/record/base/class_methods_spec.rb create mode 100644 spec/aws/record/base/dirty_tracking_spec.rb create mode 100644 spec/aws/record/base/empty_records_spec.rb create mode 100644 spec/aws/record/base/errors_spec.rb create mode 100644 spec/aws/record/base/instance_methods_spec.rb create mode 100644 spec/aws/record/base/optimistic_locking_spec.rb create mode 100644 spec/aws/record/base/timestamps_spec.rb create mode 100644 spec/aws/record/base/validations/acceptance_spec.rb create mode 100644 spec/aws/record/base/validations/confirmation_spec.rb create mode 100644 spec/aws/record/base/validations/count_spec.rb create mode 100644 spec/aws/record/base/validations/exclusion_spec.rb create mode 100644 spec/aws/record/base/validations/format_spec.rb create mode 100644 spec/aws/record/base/validations/inclusion_spec.rb create mode 100644 spec/aws/record/base/validations/length_spec.rb create mode 100644 spec/aws/record/base/validations/numericality_spec.rb create mode 100644 spec/aws/record/base/validations/presence_spec.rb create mode 100644 spec/aws/record/scope_spec.rb create mode 100644 spec/aws/record_spec.rb create mode 100644 spec/aws/resource_cache_spec.rb create mode 100644 spec/aws/response_cache_spec.rb create mode 100644 spec/aws/response_spec.rb create mode 100644 spec/aws/s3/access_control_list_spec.rb create mode 100644 spec/aws/s3/bucket_collection_spec.rb create mode 100644 spec/aws/s3/bucket_spec.rb create mode 100644 spec/aws/s3/bucket_version_collection_spec.rb create mode 100644 spec/aws/s3/client/xml_spec.rb create mode 100644 spec/aws/s3/client_spec.rb create mode 100644 spec/aws/s3/errors_spec.rb create mode 100644 spec/aws/s3/multipart_upload_collection_spec.rb create mode 100644 spec/aws/s3/multipart_upload_spec.rb create mode 100644 spec/aws/s3/object_collection_spec.rb create mode 100644 spec/aws/s3/object_metadata_spec.rb create mode 100644 spec/aws/s3/object_upload_collection_spec.rb create mode 100644 spec/aws/s3/object_version_collection_spec.rb create mode 100644 spec/aws/s3/object_version_spec.rb create mode 100644 spec/aws/s3/policy_spec.rb create mode 100644 spec/aws/s3/presigned_post_spec.rb create mode 100644 spec/aws/s3/request_spec.rb create mode 100644 spec/aws/s3/s3_object/copy_spec.rb create mode 100644 spec/aws/s3/s3_object_spec.rb create mode 100644 spec/aws/s3/tree/branch_node_spec.rb create mode 100644 spec/aws/s3/tree/child_collection_spec.rb create mode 100644 spec/aws/s3/tree/leaf_node_spec.rb create mode 100644 spec/aws/s3/tree/node_spec.rb create mode 100644 spec/aws/s3/tree_spec.rb create mode 100644 spec/aws/s3/uploaded_part_collection_spec.rb create mode 100644 spec/aws/s3/uploaded_part_spec.rb create mode 100644 spec/aws/s3_spec.rb create mode 100644 spec/aws/simple_db/attribute_collection_spec.rb create mode 100644 spec/aws/simple_db/attribute_spec.rb create mode 100644 spec/aws/simple_db/client/xml_spec.rb create mode 100644 spec/aws/simple_db/client_spec.rb create mode 100644 spec/aws/simple_db/domain_collection_spec.rb create mode 100644 spec/aws/simple_db/domain_metadata_spec.rb create mode 100644 spec/aws/simple_db/domain_spec.rb create mode 100644 spec/aws/simple_db/errors_spec.rb create mode 100644 spec/aws/simple_db/item_collection_spec.rb create mode 100644 spec/aws/simple_db/item_data_spec.rb create mode 100644 spec/aws/simple_db/item_spec.rb create mode 100644 spec/aws/simple_db/request_spec.rb create mode 100644 spec/aws/simple_db_spec.rb create mode 100644 spec/aws/simple_email_service/client/xml_spec.rb create mode 100644 spec/aws/simple_email_service/client_spec.rb create mode 100644 spec/aws/simple_email_service/email_address_collection_spec.rb create mode 100644 spec/aws/simple_email_service/quotas_spec.rb create mode 100644 spec/aws/simple_email_service/request_spec.rb create mode 100644 spec/aws/simple_email_service_spec.rb create mode 100644 spec/aws/sns/policy_spec.rb create mode 100644 spec/aws/sns/request_spec.rb create mode 100644 spec/aws/sns/subscription_collection_spec.rb create mode 100644 spec/aws/sns/subscription_spec.rb create mode 100644 spec/aws/sns/topic_collection_spec.rb create mode 100644 spec/aws/sns/topic_spec.rb create mode 100644 spec/aws/sns/topic_subscription_collection_spec.rb create mode 100644 spec/aws/sns_spec.rb create mode 100644 spec/aws/sqs/client_spec.rb create mode 100644 spec/aws/sqs/policy_spec.rb create mode 100644 spec/aws/sqs/queue_collection_spec.rb create mode 100644 spec/aws/sqs/queue_spec.rb create mode 100644 spec/aws/sqs/received_message_spec.rb create mode 100644 spec/aws/sqs/received_sns_message_spec.rb create mode 100644 spec/aws/sqs/request_spec.rb create mode 100644 spec/aws/sqs_spec.rb create mode 100644 spec/aws/xml_grammar_spec.rb create mode 100644 spec/aws_spec.rb create mode 100644 spec/shared/accepts_configuration_examples.rb create mode 100644 spec/shared/aws_client_examples.rb create mode 100644 spec/shared/aws_record_examples.rb create mode 100644 spec/shared/blocking_handler_examples.rb create mode 100644 spec/shared/class_methods_module_examples.rb create mode 100644 spec/shared/collection_examples.rb create mode 100644 spec/shared/configured_grammars_module_examples.rb create mode 100644 spec/shared/ec2/collection_array_access_examples.rb create mode 100644 spec/shared/ec2/collection_examples.rb create mode 100644 spec/shared/ec2/describe_call_attribute_examples.rb create mode 100644 spec/shared/ec2/exists_method_examples.rb create mode 100644 spec/shared/ec2/fitlered_collection_examples.rb create mode 100644 spec/shared/ec2/model_object_examples.rb create mode 100644 spec/shared/ec2/public_private_accessor_examples.rb create mode 100644 spec/shared/ec2/reserved_instances_attributes_examples.rb create mode 100644 spec/shared/ec2/resource_accessor_examples.rb create mode 100644 spec/shared/ec2/resource_examples.rb create mode 100644 spec/shared/ec2/tagged_collection_examples.rb create mode 100644 spec/shared/ec2/tagged_item_examples.rb create mode 100644 spec/shared/enumerable_examples.rb create mode 100644 spec/shared/indifferent_access_examples.rb create mode 100644 spec/shared/pagination_examples.rb create mode 100644 spec/shared/policy_examples.rb create mode 100644 spec/shared/record/validation_examples.rb create mode 100644 spec/shared/request_examples.rb create mode 100644 spec/shared/resource_examples.rb create mode 100644 spec/shared/s3/acl_examples.rb create mode 100644 spec/shared/s3/model_object_examples.rb create mode 100644 spec/shared/s3/paginated_collection_examples.rb create mode 100644 spec/shared/s3/prefixed_collection_examples.rb create mode 100644 spec/shared/s3/tree_like_examples.rb create mode 100644 spec/shared/simple_db/accepts_consistent_read_option_examples.rb create mode 100644 spec/shared/simple_db/expect_condition_examples.rb create mode 100644 spec/shared/simple_db/model_object_examples.rb create mode 100644 spec/shared/sqs/model_object_examples.rb create mode 100644 spec/shared/stub_config_examples.rb create mode 100644 spec/shared/uses_cached_responses_examples.rb create mode 100644 spec/spec_helper.rb create mode 100644 tasks/cucumber.rake create mode 100644 tasks/docs.rake create mode 100644 tasks/flog.rake create mode 100644 tasks/gems.rake create mode 100644 tasks/rcov.rake create mode 100644 tasks/rspec.rake create mode 100644 tasks/version.rb diff --git a/.yardopts b/.yardopts new file mode 100644 index 00000000000..70b38773573 --- /dev/null +++ b/.yardopts @@ -0,0 +1,6 @@ +--no-private +--files guides/* +--files NOTICE.txt +--files LICENSE.txt +--exclude '~$' +--title 'AWS SDK for Ruby' diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000000..effb9e8e5e7 --- /dev/null +++ b/Gemfile @@ -0,0 +1,58 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +source 'http://rubygems.org' + +gem 'uuidtools', '~> 2.1' +gem 'httparty', '~> 0.7' +gem 'nokogiri', '~> 1.4' +gem 'json', '~> 1.4.6' + +gem 'jruby-openssl', :platforms => :jruby +gem 'ruby-debug', :platforms => [:jruby, :mri_18] + +group :autotest do + gem 'ZenTest', '~> 4.4' + gem 'autotest-fsevent', '~> 0.2.4' + gem 'autotest-growl', '~> 0.2.9' + gem 'guard' + gem 'guard-rspec' + gem 'rb-fsevent' + gem 'growl' +end + +group :build do + gem 'rspec', '2.5' + gem 'rcov', '0.9.9' + gem 'hanna', '0.1.12', :require => 'hanna/rdoctask' + gem 'rspec', '2.5', :require => 'rspec/core/rake_task' + gem 'ci_reporter', '~> 1.6', :require => 'ci/reporter/rake/rspec' + gem 'flog', '~> 2.5' + gem 'yard', '~> 0.6.8' +end + +group :profile do + gem 'ruby-prof' +end + +group :integration do + gem 'cucumber', '~> 0.10.2', :require => 'cucumber/rake/task' + gem 'bourne', '1.0' + gem 'mocha', '0.9.8' + gem 'net-ssh', '~> 2.1' + gem 'multipart-post', '~> 1.1.2' +end + +group :curb do + gem 'curb', '~> 0.7' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000000..1cb11486d77 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,115 @@ +GEM + remote: http://rubygems.org/ + specs: + ZenTest (4.5.0) + autotest-fsevent (0.2.5) + sys-uname + autotest-growl (0.2.9) + bouncy-castle-java (1.5.0146.1) + bourne (1.0) + mocha (= 0.9.8) + builder (2.1.2) + ci_reporter (1.6.5) + builder (>= 2.1.2) + columnize (0.3.4) + crack (0.1.8) + cucumber (0.10.2) + builder (>= 2.1.2) + diff-lcs (>= 1.1.2) + gherkin (>= 2.3.5) + json (>= 1.4.6) + term-ansicolor (>= 1.0.5) + curb (0.7.15) + diff-lcs (1.1.2) + flog (2.5.1) + ruby_parser (~> 2.0) + sexp_processor (~> 3.0) + gherkin (2.3.6) + json (>= 1.4.6) + gherkin (2.3.6-java) + json (>= 1.4.6) + growl (1.0.3) + guard (0.5.0) + thor (~> 0.14.6) + guard-rspec (0.4.0) + guard (>= 0.4.0) + haml (2.2.24) + hanna (0.1.12) + haml (~> 2.2.8) + rake (~> 0.8.2) + rdoc (~> 2.3.0) + httparty (0.7.8) + crack (= 0.1.8) + jruby-openssl (0.7.4) + bouncy-castle-java + json (1.4.6) + json (1.4.6-java) + linecache (0.46) + rbx-require-relative (> 0.0.4) + mocha (0.9.8) + rake + multipart-post (1.1.2) + net-ssh (2.1.4) + nokogiri (1.5.0) + nokogiri (1.5.0-java) + rake (0.8.7) + rb-fsevent (0.4.1) + rbx-require-relative (0.0.5) + rcov (0.9.9) + rcov (0.9.9-java) + rdoc (2.3.0) + rspec (2.5.0) + rspec-core (~> 2.5.0) + rspec-expectations (~> 2.5.0) + rspec-mocks (~> 2.5.0) + rspec-core (2.5.2) + rspec-expectations (2.5.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.5.0) + ruby-debug (0.10.4) + columnize (>= 0.1) + ruby-debug-base (~> 0.10.4.0) + ruby-debug-base (0.10.4) + linecache (>= 0.3) + ruby-debug-base (0.10.4-java) + ruby-prof (0.10.8) + ruby_parser (2.0.6) + sexp_processor (~> 3.0) + sexp_processor (3.0.5) + sys-uname (0.8.5) + term-ansicolor (1.0.5) + thor (0.14.6) + uuidtools (2.1.2) + yard (0.6.8) + +PLATFORMS + java + ruby + +DEPENDENCIES + ZenTest (~> 4.4) + autotest-fsevent (~> 0.2.4) + autotest-growl (~> 0.2.9) + bourne (= 1.0) + ci_reporter (~> 1.6) + cucumber (~> 0.10.2) + curb (~> 0.7) + flog (~> 2.5) + growl + guard + guard-rspec + hanna (= 0.1.12) + httparty (~> 0.7) + jruby-openssl + json (~> 1.4.6) + mocha (= 0.9.8) + multipart-post (~> 1.1.2) + net-ssh (~> 2.1) + nokogiri (~> 1.4) + rb-fsevent + rcov (= 0.9.9) + rspec (= 2.5) + ruby-debug + ruby-prof + uuidtools (~> 2.1) + yard (~> 0.6.8) diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000000..6eadf084af2 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,171 @@ +Apache License +Version 2.0, January 2004 + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the +copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other +entities that control, are controlled by, or are under common control +with that entity. For the purposes of this definition, "control" means +(i) the power, direct or indirect, to cause the direction or +management of such entity, whether by contract or otherwise, or (ii) +ownership of fifty percent (50%) or more of the outstanding shares, or +(iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but not +limited to compiled object code, generated documentation, and +conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object +form, made available under the License, as indicated by a copyright +notice that is included in or attached to the work (an example is +provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the +purposes of this License, Derivative Works shall not include works +that remain separable from, or merely link (or bind by name) to the +interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the +original version of the Work and any modifications or additions to +that Work or Derivative Works thereof, that is intentionally submitted +to Licensor for inclusion in the Work by the copyright owner or by an +individual or Legal Entity authorized to submit on behalf of the +copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent to +the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control +systems, and issue tracking systems that are managed by, or on behalf +of, the Licensor for the purpose of discussing and improving the Work, +but excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, publicly +display, publicly perform, sublicense, and distribute the Work and +such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except +as stated in this section) patent license to make, have made, use, +offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such +Contributor that are necessarily infringed by their Contribution(s) +alone or by combination of their Contribution(s) with the Work to +which such Contribution(s) was submitted. If You institute patent +litigation against any entity (including a cross-claim or counterclaim +in a lawsuit) alleging that the Work or a Contribution incorporated +within the Work constitutes direct or contributory patent +infringement, then any patent licenses granted to You under this +License for that Work shall terminate as of the date such litigation +is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work +or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You meet +the following conditions: + + 1. You must give any other recipients of the Work or Derivative + Works a copy of this License; and + 2. You must cause any modified files to carry prominent notices + stating that You changed the files; and + 3. You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, excluding + those notices that do not pertain to any part of the Derivative + Works; and + 4. If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained within + such NOTICE file, excluding those notices that do not pertain to + any part of the Derivative Works, in at least one of the following + places: within a NOTICE text file distributed as part of the + Derivative Works; within the Source form or documentation, if + provided along with the Derivative Works; or, within a display + generated by the Derivative Works, if and wherever such third-party + notices normally appear. The contents of the NOTICE file are for + informational purposes only and do not modify the License. You may + add Your own attribution notices within Derivative Works that You + distribute, alongside or as an addendum to the NOTICE text from the + Work, provided that such additional attribution notices cannot be + construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may +provide additional or different license terms and conditions for use, +reproduction, or distribution of Your modifications, or for any such +Derivative Works as a whole, provided Your use, reproduction, and +distribution of the Work otherwise complies with the conditions stated +in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work by +You to the Licensor shall be under the terms and conditions of this +License, without any additional terms or conditions. Notwithstanding +the above, nothing herein shall supersede or modify the terms of any +separate license agreement you may have executed with Licensor +regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed +to in writing, Licensor provides the Work (and each Contributor +provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied, including, without +limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, +MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely +responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your +exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, unless +required by applicable law (such as deliberate and grossly negligent +acts) or agreed to in writing, shall any Contributor be liable to You +for damages, including any direct, indirect, special, incidental, or +consequential damages of any character arising as a result of this +License or out of the use or inability to use the Work (including but +not limited to damages for loss of goodwill, work stoppage, computer +failure or malfunction, or any and all other commercial damages or +losses), even if such Contributor has been advised of the possibility +of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, and +charge a fee for, acceptance of support, warranty, indemnity, or other +liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only on +Your own behalf and on Your sole responsibility, not on behalf of any +other Contributor, and only if You agree to indemnify, defend, and +hold each Contributor harmless for any liability incurred by, or +claims asserted against, such Contributor by reason of your accepting +any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 00000000000..a7a20c3eba1 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,2 @@ +AWS SDK for Ruby +Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 00000000000..65fc0f0a76f --- /dev/null +++ b/README.rdoc @@ -0,0 +1,189 @@ += Getting Started with the AWS SDK for Ruby + +The AWS SDK for Ruby helps you to get started building applications +using AWS infrastructure services, including Amazon Simple Storage +Service (Amazon S3), Amazon Elastic Compute Cloud (Amazon EC2), Amazon +SimpleDB, and more. This guide shows how you can start building Ruby +and Rails applications on the Amazon Web Services platform with the +AWS Ruby gem. + += Get Set Up + +To get set up, you must sign up for Amazon Web Services, get your AWS +credentials, and set up your environment. + +== Sign Up for AWS Products + +Before you can begin, you must sign up for each AWS product you want +to use. The sample included in the SDK uses Amazon S3, so we'll use +that product as an example here. + +=== To sign up for a product + +1. Go to the home page for the product, for example + http://aws.amazon.com/s3. + + *Tip:* Go to http://aws.amazon.com/products for a list of links to all our products. + +2. Click the sign-up button on the top right corner of the page. + +3. Follow the on-screen instructions. If you don't already have an AWS + account, you are prompted to create one as part of the sign-up + process. + +AWS sends you a confirmation e-mail after the sign-up process is +complete. You can view your current account activity or manage your +account at any time, by going to http://aws.amazon.com and clicking +the *Account* tab. + +== Get Your Credentials + +To use the AWS SDK for Ruby, you need your AWS Access Key ID and Secret Access Key. + +=== To get your AWS Access Key ID and Secret Access Key + +1. Go to http://aws.amazon.com. + +2. Click *Account* and then click Security Credentials. + The Security Credentials page displays (you might be prompted to log in). + +3. Scroll down to Access Credentials and make sure the Access Keys tab + is selected. The AWS Access Key ID appears in the Access Key column. + +4. To view the Secret Access Key, click *Show*. + + *Important!* Your Secret Access Key is a secret, which only you and + AWS should know. It is important to keep it confidential to protect + your account. Store it securely in a safe place. Never include it in + your requests to AWS, and never e-mail it to anyone. Do not share it + outside your organization, even if an inquiry appears to come from AWS + or Amazon.com. No one who legitimately represents Amazon will ever ask + you for your Secret Access Key. + +== Set Up Your Environment + +The AWS Ruby gem runs on Ruby 1.8.7 and later. If you have an older +version of Ruby, RVM is a great way to get started using the latest +version. + += Install the SDK + +To install the AWS Ruby gem, just enter: + + gem install aws-sdk + += Run the Samples + +Now that you've installed the gem, you can run the samples, which you +can find in our GitHub repository: + + $ git clone git://github.com/amazonwebservices/aws-sdk-for-ruby.git + $ cd aws-sdk-for-ruby/samples/ + +The subdirectories of the +samples+ directory contain several code +samples that you can run. These samples demonstrate basic usage of +the SDK features. + +== To run the Amazon S3 Sample + +1. Create a file named config.yml in the samples directory as follows: + + # Fill in your AWS Access Key ID and Secret Access Key + # http://aws.amazon.com/security-credentials + access_key_id: REPLACE_WITH_ACCESS_KEY_ID + secret_access_key: REPLACE_WITH_SECRET_ACCESS_KEY + +2. Run a sample script with the Ruby interpreter. For example, to run + the s3/upload_file.rb sample: + + $ echo "Hello, World!" > helloworld.txt + $ ruby s3/upload_file.rb unique-bucket-name helloworld.txt + +== To use the AWS ORM in a Rails 3 application + +1. Install the gem: + + $ gem install aws-sdk + +2. Start a new Rails project: + + $ gem install rails + $ rails new myapp + $ cd myapp/ + +3. Add the following line to your Gemfile: + + gem 'aws-sdk' + +4. Install dependencies: + + $ bundle install + +5. Create config/aws.yml as follows: + + # Fill in your AWS Access Key ID and Secret Access Key + # http://aws.amazon.com/security-credentials + access_key_id: REPLACE_WITH_ACCESS_KEY_ID + secret_access_key: REPLACE_WITH_SECRET_ACCESS_KEY + +6. Create config/initializers/aws.rb as follows: + + # load the libraries + require 'aws' + # log requests using the default rails logger + AWS.config(:logger => Rails.logger) + # load credentials from a file + config_path = File.expand_path(File.dirname(__FILE__)+"/../aws.yml") + AWS.config(YAML.load(File.read(config_path))) + +7. Create app/models/my_record.rb as follows: + + class MyRecord < AWS::Record::Base + string_attr :name + end + +8. Create the SimpleDB domain: + + $ rails console + > MyRecord.create_domain + +Now, you can play around with the model by creating some records and querying them: + + > MyRecord.find(:all).to_a + => [] + > MyRecord.new(:name => "The first one").save + => true + > MyRecord.new(:name => "The second one").save + => true + > MyRecord.where('name like ?', "%first%").count + => 1 + +Exit the rails console before continuing to the next step: + + > exit + +9. Generate a scaffold controller for your model: + + $ rails generate scaffold_controller MyRecord name:string + $ rails server + +10. Add a route to your scaffold controller in config/routes.rb: + + Myapp::Application.routes.draw do + # add this line: + resources :my_records + end + +11. Now, you can create records in the browser at http://localhost:3000/my_records. + += Where Do I Go from Here? + +For more information about the AWS SDK for Ruby, including a complete +list of supported AWS products, go to +http://aws.amazon.com/sdkforruby. + +The SDK reference documentation provides information about both the +AWS Ruby gem and AWS Rails integration gem. You can find it at +http://docs.amazonwebservices.com/AWSRubySDK/latest. + +Licensed under Apache 2.0. See {file:LICENSE.txt} and {file:NOTICE.txt} files. diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000000..14aaa041041 --- /dev/null +++ b/Rakefile @@ -0,0 +1,21 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'bundler/setup' +Bundler.require(:default, :build, :integration) + +tasks_dir = File.join(File.dirname(__FILE__), "tasks") +$:.unshift(tasks_dir) +Dir[File.join(tasks_dir, "**", "*.rake")].each do |task_file| + load task_file +end diff --git a/features/ec2/combined.feature b/features/ec2/combined.feature new file mode 100644 index 00000000000..94e7c55b0d1 --- /dev/null +++ b/features/ec2/combined.feature @@ -0,0 +1,39 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +@ec2 @combined +Feature: Combining various EC2 features + + As a user of the high-level interface for EC2 + I want to use many parts of the interface at the same time + + @instances @elastic_ips @security_groups @key_pairs @slow + Scenario: Starting an instance with a new key pair and logging in + + Given I create a security group named "ruby-integration-test-ssh" + And I add SSH access from my current IP to the security group + And I create a key pair named "ruby-integration-test" + And I allocate an elastic ip + + When I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | key_name | ruby-integration-test | + | security_groups | ruby-integration-test-ssh | + And the instance status should eventually be "running" + And I associate the elastic ip with the instance + And The instance should eventually have the new elastic ip + + Then I should be able to ssh to the elastic ip as "ec2-user" + And I terminate the instance + And the instance status should eventually be "terminated" diff --git a/features/ec2/elastic_ips.feature b/features/ec2/elastic_ips.feature new file mode 100644 index 00000000000..9eae14e0710 --- /dev/null +++ b/features/ec2/elastic_ips.feature @@ -0,0 +1,49 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +@ec2 @elastic_ips +Feature: EC2 Elastic IPs + + As a user of the high-level interface for EC2 + I want to manage elastic ip addresses + + Scenario: Allocating an elastic ip address + When I allocate an elastic ip + And I list allocated elastic ips + Then The list should contain the elastic ip + + Scenario: Releasing an elastic ip address + Given I allocate an elastic ip + When I release the elastic ip address + And I list allocated elastic ips + Then The list should not contain the elastic ip + + @slow + Scenario: Associating an elastic ip address with an instance + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And the instance status should eventually be "running" + And I allocate an elastic ip + When I associate the elastic ip with the instance + Then The instance should eventually have the new elastic ip + + @memoized + Scenario: Listing elastic ip addresses with memoization + Given I allocate an elastic ip + And I start a memoization block + When I compute a map of public IP address to instance ID + And I count the elastic IPs in my account + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeAddresses | diff --git a/features/ec2/errors.feature b/features/ec2/errors.feature new file mode 100644 index 00000000000..8bc7cde1928 --- /dev/null +++ b/features/ec2/errors.feature @@ -0,0 +1,28 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @errors +Feature: EC2 modeled errors + + I want to be able to rescue named errors. + + Scenario Outline: Rescue InvalidInstanceID.NotFound + Given I ask for the instance "i-123" by ID + When I terminate the instance rescuing "" + Then I should rescue the error with code "" + + Examples: + | class | code | + | EC2::Errors::InvalidInstanceID::NotFound | InvalidInstanceID.NotFound | + | Errors::ClientError | InvalidInstanceID.NotFound | diff --git a/features/ec2/image_attributes.feature b/features/ec2/image_attributes.feature new file mode 100644 index 00000000000..4e31d0bcb96 --- /dev/null +++ b/features/ec2/image_attributes.feature @@ -0,0 +1,285 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @image_attributes +Feature: Image attributes + + As a user of the high-level interface for EC2 + I want to read and write image attributes + So that I can better manage my existing machine images + + @get + Scenario: Get image location + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image location should eventually be "aws-sdk-amis/quickstart/image.manifest.xml" + + @get + Scenario: Get image state + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image state should eventually be :available + + @get + Scenario: Get image owner ID + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image owner ID should eventually be the account ID + + @get + Scenario: Get image owner alias + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image owner alias should eventually be nil + + @get + Scenario: Ask if image is public + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + When I ask if the image is public + Then the result should be false + + @get + Scenario: Get image launch permissions + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image launch permissions should eventually be empty + + @set + Scenario: Make image public + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + When I make the image public + Then the image launch permissions should eventually be empty + And the image should be public + + @set + Scenario: Remove explicit permission + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + And I add image launch permissions for the user "599169622985" + And the image launch permissions should eventually include: + | 599169622985 | + When I remove image launch permissions for the user "599169622985" + Then the image launch permissions should eventually be empty + + @reset + Scenario: Reset launch permissions + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + And I add image launch permissions for the user "599169622985" + When I reset the image launch permissions + Then the image launch permissions should eventually be empty + + @set + Scenario: Add explicit permission + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + When I add image launch permissions for the user "599169622985" + Then the image launch permissions should eventually include: + | 599169622985 | + + @memoized @set + Scenario: Memoized launch permissions + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + When I add image launch permissions for the user "599169622985" + And I start a memoization block + And I get the image launch permissions + And I get the image launch permissions again + Then the image launch permissions should eventually include: + | 599169622985 | + And exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeImageAttribute | + | param | Attribute | launchPermission | + + @get + Scenario: Get image architecture + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + | architecture | i386 | + And I wait for the image to exist + Then the image architecture should eventually be :i386 + + @get + Scenario: Get image type + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image type should eventually be :machine + + @get + Scenario: Get kernel ID + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + | kernel_id | aki-f5c1219c | + And I wait for the image to exist + Then the image kernel ID should eventually be "aki-f5c1219c" + + @get + Scenario: Get ramdisk ID + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + | ramdisk_id | ari-dbc121b2 | + And I wait for the image to exist + Then the image ramdisk ID should eventually be "ari-dbc121b2" + + @get + Scenario: Get platform + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image platform should eventually be nil + + # TODO: figure out how to get something to show up in this list + #Scenario: Get state reason + # Given I create an image from an invalid snapshot ID + # Then the image state change reasons should eventually include: + # | code | message | + # | Client.InvalidSnapshot.NotFound | The specified snapshot was not found. | + + @get + Scenario: Get image name + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image name should eventually be "my-image" + + @get + Scenario: Get image description + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | description | foobar | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image description should eventually be "foobar" + + @set + Scenario: Set image description + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + When I set the image description to "foobar" + Then the image description should eventually be "foobar" + + @get + Scenario: Get image root device type + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image root device type should eventually be :instance_store + + @get + Scenario: Get image root device name + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image root device name should eventually be "/dev/sda1" + + @get + Scenario: Get image virtualization type + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image virtualization type should eventually be :paravirtual + + @get + Scenario: Get image hypervisor + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And I wait for the image to exist + Then the image hypervisor should eventually be :xen + + @get + Scenario: Get image block device mapping + Given I create an image with the following block device mappings: + """ + { + "/dev/sda1" => { + :snapshot_id => "snap-1482e87d", + :volume_size => 15, + :delete_on_termination => true + }, + "/dev/sda2" => "ephemeral0" + } + """ + And I wait for the image to exist + When the image block device mappings should eventually have the following mappings: + | device name | delete on termination | + | /dev/sda1 | true | + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | RegisterImage | + | param | BlockDeviceMapping.1.DeviceName | /dev/sda1 | + | param | BlockDeviceMapping.1.Ebs.SnapshotId | snap-1482e87d | + | param | BlockDeviceMapping.1.Ebs.DeleteOnTermination | true | + | param | BlockDeviceMapping.2.DeviceName | /dev/sda2 | + | param | BlockDeviceMapping.2.VirtualName | ephemeral0 | diff --git a/features/ec2/images.feature b/features/ec2/images.feature new file mode 100644 index 00000000000..1f49beeee45 --- /dev/null +++ b/features/ec2/images.feature @@ -0,0 +1,122 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @images +Feature: Basic Image Operations + + As a user of the high-level interface for EC2 + I want to create, list, and deregister images + So that I can make my instances repeatable. + + @slow + Scenario: Create EBS-backed image + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance status to be "running" + When I create an image from the instance with the following parameters: + | parameter | value | + | name | my-image | + | description | the one I just made | + | no_reboot | true | + Then the result should be an image + And the image should eventually be in the list of images I own + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | CreateImage | + | param_match | InstanceId | i-.+ | + | param | Name | my-image | + | param | Description | the one I just made | + | param | NoReboot | true | + + Scenario: Register S3-backed image + When I create an image with the following parameters: + | parameter | value | + | name | my-image | + | description | the s3-backed one I just made | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + | architecture | i386 | + | kernel_id | aki-12f0127b | + | ramdisk_id | ari-0ccd3965 | + Then the result should be an image + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | RegisterImage | + | param | ImageLocation | aws-sdk-amis/quickstart/image.manifest.xml | + | param | Name | my-image | + | param | Description | the s3-backed one I just made | + | param | Architecture | i386 | + | param | KernelId | aki-12f0127b | + | param | RamdiskId | ari-0ccd3965 | + + Scenario: List images + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + When I ask for the list of images owned by me + Then the image I created should be in the list + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeImages | + | param | Owner.1 | self | + + @memoized + Scenario: List images with memoization + Given I start a memoization block + When I compute a hash of image name to image ID for Amazon-owned images + And I sort the list of Amazon-owned images by image location + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeImages | + + @memoized + Scenario: List image EBS information with memoization + Given I start a memoization block + When I compute a hash of mapped snapshot ID to image ID for Amazon-owned images + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeImages | + And no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeImageAttribute | + + Scenario: Deregister image + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + When I deregister the image + Then the image should eventually not exist + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DeregisterImage | + | param_match | ImageId | ami-.+ | + + Scenario: Get image by ID + When I ask for the image "ami-123" by ID + Then the result should be an image + + Scenario: Check that an image exists (does not exist) + Given I ask for the image "ami-123" by ID + When I ask if the image exists + Then the result should be false + + Scenario: Check that an image exists (exists) + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + When I ask if the image exists + Then the result should eventually be true diff --git a/features/ec2/instance_attributes.feature b/features/ec2/instance_attributes.feature new file mode 100644 index 00000000000..22c832adc20 --- /dev/null +++ b/features/ec2/instance_attributes.feature @@ -0,0 +1,409 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @instance_attributes +Feature: Instance attributes + + As a user of the high-level interface for EC2 + I want to read, modify, and reset instance attributes + So that I can use the advanced features of EC2 + + @get + Scenario: Get user data + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | user_data | HELLO GOODBYE! | + And I wait for the instance to exist + When I get the instance's user data + Then the result should be "HELLO GOODBYE!" + + @set @slow + Scenario: Set user data + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | user_data | HELLO GOODBYE! | + And I put the instance into a stopped state + When I set the instance's user data to "something else" + Then the instance's user data should eventually be "something else" + + @get + Scenario: Get instance type + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | instance_type | t1.micro | + And I wait for the instance to exist + When I get the instance type + Then the result should be "t1.micro" + + @get @memoized + Scenario: Get memoized attributes from RunInstances + Given I start a memoization block + And I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + When I get the following attributes of the instance: + | instance_type | + | image | + | status | + | private_dns_name | + | public_dns_name | + | state_transition_reason | + | key_pair | + | ami_launch_index | + | launch_time | + | kernel_id | + | ramdisk_id | + | platform | + | monitoring_enabled? | + | private_ip_address | + | public_ip_address | + | security_groups | + | architecture | + | root_device_type | + | root_device_name | + | block_device_mappings | + | virtualization_type | + | client_token | + | hypervisor | + | reservation_id | + | owner_id | + | requester_id | + Then no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeInstances | + And no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeInstanceAttribute | + + @get @memoized + Scenario: Get instance type (memoized from describe attribute) + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | instance_type | t1.micro | + And I wait for the instance to exist + And I start a memoization block + When I list instances filtering by the instance ID I just created + And I get the instance type of the first item in the result + Then the result should be "t1.micro" + And 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeInstances | + And no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeInstanceAttribute | + + @get @memoized + Scenario: Get instance type (memoized from describe) + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | instance_type | t1.micro | + And I wait for the instance to exist + And I start a memoization block + When I get the instance type + And I get the instance type again + Then the result should be "t1.micro" + And 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeInstanceAttribute | + + @set @slow + Scenario: Change instance type + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | instance_type | t1.micro | + And I wait for the instance to exist + And I put the instance into a stopped state + When I change the instance type to "m1.small" + Then the instance type should eventually be "m1.small" + + @get + Scenario: Check API Termination + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | disable_api_termination | true | + And I wait for the instance to exist + When I ask if termination is disabled + Then the result should be true + + @set + Scenario: Disable API Termination + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance to exist + When I disable API termination for the instance + Then the instance should eventually have API termination disabled + + @get @vpc + Scenario: Check if Source/Destination checking is enabled + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance to exist + When I ask if source/destination checking is enabled + Then the result should be true + + @set @vpc + Scenario: Disable Source/Destination checking + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance to exist + When I disable source/destination checking + Then the instance should eventually have source/destination checking disabled + + @get + Scenario: Get instance-initiated shutdown behavior + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | instance_initiated_shutdown_behavior | stop | + And I wait for the instance to exist + When I get the instance-initiated shutdown behavior + Then the result should be "stop" + + @set + Scenario: Change instance-initiated shutdown behavior + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance to exist + When I set the instance-initiated shutdown behavior to "stop" + Then the instance-initiated shutdown behavior should eventually be "stop" + + @get + Scenario: Get image ID + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance to exist + When I get the instance's image ID + Then the result should be "ami-8c1fece5" + + @get + Scenario: Get kernel ID + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | kernel_id | aki-f5c1219c | + And I wait for the instance to exist + When I get the instance's kernel ID + Then the result should be "aki-f5c1219c" + + @set @slow + Scenario: Set kernel ID + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I put the instance into a stopped state + When I set the instance's kernel ID to "aki-f5c1219c" + Then the instance's kernel ID should eventually be "aki-f5c1219c" + + @reset @slow + Scenario: Reset kernel ID + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I put the instance into a stopped state + When I reset the instance's kernel ID + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | ResetInstanceAttribute | + | param_match | InstanceId | ^i-.+ | + | param | Attribute | kernel | + + @get + Scenario: Get ramdisk ID + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | ramdisk_id | ari-dbc121b2 | + And I wait for the instance to exist + When I get the instance's ramdisk ID + Then the result should be "ari-dbc121b2" + + @set @slow + Scenario: Set ramdisk ID + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I put the instance into a stopped state + When I set the instance's ramdisk ID to "ari-dbc121b2" + Then the instance's ramdisk ID should eventually be "ari-dbc121b2" + + @reset @slow + Scenario: Reset ramdisk ID + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I put the instance into a stopped state + When I reset the instance's ramdisk ID + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | ResetInstanceAttribute | + | param_match | InstanceId | ^i-.+ | + | param | Attribute | ramdisk | + + @get + Scenario: Get instance key pair + Given I create a key pair + And I request to run an instance of "ami-8c1fece5" using the key pair + Then the instance key pair should be the one I created it with + + @get + Scenario: Get root device name + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance to exist + When I get the instance's root device name + Then the result should be "/dev/sda1" + + @get @slow + Scenario: Get various string attributes from DescribeInstances + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance status to be "running" + When I query the attributes of the instance + Then I get values like the following from the instance: + | private_dns_name | .+\.internal | + | dns_name | ec2-.+\.amazonaws\.com | + | ami_launch_index | \d+ | + | private_ip_address | (\d+\.){3}\d+ | + | ip_address | (\d+\.){3}\d+ | + | architecture | i386 | + | root_device_type | ebs | + | virtualization_type | paravirtual | + | reservation_id | r-.+ | + | owner_id | \d+ | + | client_token | ^([0-9a-f]+-){4}[0-9a-f]+$ | + + @get + Scenario: Get availability zone + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | availability_zone | us-east-1d | + And I wait for the instance to exist + When I get the instance's availability zone + Then the result should be "us-east-1d" + + @get @slow + Scenario: Check monitoring status + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | monitoring_enabled | true | + And I wait for the instance status to be "running" + When I ask if monitoring is enabled + Then the result should be true + + @set @slow + Scenario: Enable monitoring + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance to exist + When I enable monitoring on the instance + Then the instance should eventually have monitoring enabled + + @set @slow @memoized @wip + Scenario: Memoized monitoring state from MonitorInstances + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance to exist + And I start a memoization block + And I enable monitoring on the instance + Then no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeInstances | + + @set @slow + Scenario: Disable monitoring + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | monitoring_enabled | true | + And I wait for the instance to exist + When I disable monitoring on the instance + Then the instance should eventually have monitoring disabled + + @set @slow @memoized @wip + Scenario: Memoized monitoring state from UnmonitorInstances + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | monitoring_enabled | true | + And I wait for the instance to exist + And I start a memoization block + And I disable monitoring on the instance + Then no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeInstances | + + # is this the right API to expose? + @get + Scenario: State transition reason + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance to exist + And I terminate the instance + And I wait for the instance status to be "terminated" + When I get the instance's state transition reason + Then the result should match "User initiated" + + @get + Scenario: Launch time + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance to exist + When I get the instance's launch time + Then the result should be a time within the last hour + + @get + Scenario: Get platform + Given I request to run an instance from a windows image + And I wait for the instance to exist + When I get the instance's platform + Then the result should be "windows" + + @get + Scenario: Get security groups + Given I create the following security groups: + | name | + | ruby-integration-test-1 | + | ruby-integration-test-2 | + And I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + | security_groups | ruby-integration-test-1 | + | security_groups | ruby-integration-test-2 | + And I wait for the instance to exist + When I ask for the security groups associated with the instance + Then the result should be an array + And The following security groups should be in the list + | name | + | ruby-integration-test-1 | + | ruby-integration-test-2 | diff --git a/features/ec2/instances.feature b/features/ec2/instances.feature new file mode 100644 index 00000000000..f083dbbd88f --- /dev/null +++ b/features/ec2/instances.feature @@ -0,0 +1,174 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @instances +Feature: Basic Instance Operations + + As a user of the high-level interface for EC2 + I want to run, list, and terminate EC2 instances + So that I can use EC2 + + Scenario: Run instance + When I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + Then The result should be an instance object + And the instance status should eventually be "pending" + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | RunInstances | + | param | ImageId | ami-8c1fece5 | + | param | MinCount | 1 | + | param | MaxCount | 1 | + | param_match | ClientToken | ^([0-9a-f]+-){4}[0-9a-f]+$ | + + Scenario: Run multiple instances + When I request to run between 1 and 10 instances with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + Then The result should be an array of instance objects + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | RunInstances | + | param | ImageId | ami-8c1fece5 | + | param | MinCount | 1 | + | param | MaxCount | 10 | + + Scenario: Terminate instance + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + When I terminate the instance + Then the instance status should eventually be "terminated" + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | TerminateInstances | + | param_match | InstanceId.1 | i-[0-9a-f]+ | + + @memoized + Scenario: Memoized instance state change from terminate + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I start a memoization block + When I get the instance status + And I terminate the instance + Then the instance status should eventually be "shutting_down" + And exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeInstances | + + @slow + Scenario: Reboot instance + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance status to be "running" + When I reboot the instance + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | RebootInstances | + | param_match | InstanceId.1 | i-[0-9a-f]+ | + + @slow + Scenario: Stop instance + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance status to be "running" + When I stop the instance + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | StopInstances | + | param_match | InstanceId.1 | i-[0-9a-f]+ | + + @memoized @slow + Scenario: Memoized instance state change from stop + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance status to be "running" + And I start a memoization block + When I stop the instance + Then the instance status should eventually be "stopping" + And no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeInstances | + + @slow + Scenario: Start instance + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance status to be "running" + And I stop the instance + And I wait for the instance status to be "stopped" + When I start the instance + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | StartInstances | + | param_match | InstanceId.1 | i-[0-9a-f]+ | + + @memoized @slow + Scenario: Memoized instance state change from start + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I wait for the instance status to be "running" + And I stop the instance + And I wait for the instance status to be "stopped" + And I start a memoization block + When I start the instance + Then the instance status should eventually be "pending" + And no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeInstances | + + Scenario: List instances + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + When I ask for the list of instances + Then the instance I started should be in the list + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeInstances | + + @memoized + Scenario: List instances memoization + Given I start a memoization block + And I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + When I compute a hash of instance IDs mapped to instance status + And I get a list of instances sorted by launch time + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeInstances | + + Scenario: Get instance by id + When I ask for the instance "i-123" by ID + Then the result should be an instance object + + Scenario: Check that an instance exists (does not exist) + Given I ask for the instance "i-123" by ID + When I ask if the instance exists + Then the result should be false + + Scenario: Check that an instance exists (exists) + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + When I ask if the instance exists + Then the result should eventually be true diff --git a/features/ec2/key_pairs.feature b/features/ec2/key_pairs.feature new file mode 100644 index 00000000000..99df02feb5b --- /dev/null +++ b/features/ec2/key_pairs.feature @@ -0,0 +1,89 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +@ec2 @key_pairs +Feature: EC2 Key Pairs + + As a user of the high-level interface for EC2 + I want to manage key pairs + + Scenario: Creating a key pair + When I create a key pair + And I list key pairs + Then The list should contain the key pair + + @memoized + Scenario: Memoized key pair fingerprint from create + Given I start a memoization block + And I create a key pair + When I get the key pair fingerprint + Then no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeKeyPairs | + + Scenario: Deleting key pairs + Given I create a key pair + When I delete the key pair + And I list key pairs + Then The list should not contain the key pair + + Scenario: Importing a key pair + Given I import a key pair named "ruby-integration-test" with the public key: + """ +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAxWZwO5spSGNes3HiJJNC7VtnlfHF0dcSUrtcE4qzMl6AsQIVX4oRu+R2UAcB8o7Gb+hdhO8HpDBNNyb9T1gs1Na302wDmDSC+0w6FO3xr/UWo9zA9MyryDPCJCxhKBWrcNKyQOiS7tGyzxnpq/m6qSWdrKwz6Qw3H+TEW9btjyKcu8huJvjNMPCrGQgB41feGTZyVXydTeRINw5zwbNOAlpD33aDSgNXh+If0FEbraBH54QSxrw4niKv+KjqBMm8QkGcsI4mvzJuVMvJjDDclbSqWWBMkqWroUqVi0t/cWJK10VVPNipKJkKUWgBCFMhgNp7CYawcQF/DBK8zhbqiQ== EXAMPLE + """ + When I get the key pair named "ruby-integration-test" + Then It should have the fingerprint: + """ + 29:23:59:44:7c:c5:66:86:60:c1:92:cc:7b:11:49:e9 + """ + + @memoized + Scenario: Memoized key pair fingerprint from import + Given I start a memoization block + And I import a key pair named "ruby-integration-test" with the public key: + """ +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAxWZwO5spSGNes3HiJJNC7VtnlfHF0dcSUrtcE4qzMl6AsQIVX4oRu+R2UAcB8o7Gb+hdhO8HpDBNNyb9T1gs1Na302wDmDSC+0w6FO3xr/UWo9zA9MyryDPCJCxhKBWrcNKyQOiS7tGyzxnpq/m6qSWdrKwz6Qw3H+TEW9btjyKcu8huJvjNMPCrGQgB41feGTZyVXydTeRINw5zwbNOAlpD33aDSgNXh+If0FEbraBH54QSxrw4niKv+KjqBMm8QkGcsI4mvzJuVMvJjDDclbSqWWBMkqWroUqVi0t/cWJK10VVPNipKJkKUWgBCFMhgNp7CYawcQF/DBK8zhbqiQ== EXAMPLE + """ + When I get the key pair named "ruby-integration-test" + Then It should have the fingerprint: + """ + 29:23:59:44:7c:c5:66:86:60:c1:92:cc:7b:11:49:e9 + """ + Then no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeKeyPairs | + + Scenario: Get key pair by name + When I get the key pair named "foo" + Then the result should be an key pair object + + Scenario: Check that an key pair exists (does not exist) + Given I get the key pair named "xyzzy-does-not-exist" + When I ask if the key pair exists + Then the result should be false + + Scenario: Check that an key pair exists (exists) + Given I create a key pair + When I ask if the key pair exists + Then the result should be true + + @memoized + Scenario: Listing key pairs with memoization + Given I create a key pair + And I start a memoization block + When I compute a map of key name to key fingerprint + And I count the key pairs in my account + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeKeyPairs | diff --git a/features/ec2/regions.feature b/features/ec2/regions.feature new file mode 100644 index 00000000000..7952ecefdae --- /dev/null +++ b/features/ec2/regions.feature @@ -0,0 +1,53 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @regions +Feature: Regions + + As a user of the high-level interface for EC2 + I want to code against the various EC2 regional endpoints + So that I can use different regions for my application + + Scenario: List regions + When I ask for the list of EC2 regions + Then the result should contain the following region information: + | name | endpoint | + | us-east-1 | ec2.us-east-1.amazonaws.com | + | eu-west-1 | ec2.eu-west-1.amazonaws.com | + + @memoized + Scenario: List regions + Given I start a memoization block + When I compute a hash of region name to region endpoint + And I count the regions available to my account + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeRegions | + + Scenario: Check that a region exists (does not exist) + Given I ask for the region "mars-west-12" by name + When I ask if the region exists + Then the result should be false + + Scenario: Check that a region exists (exists) + Given I ask for the first region + When I ask if the region exists + Then the result should be true + + Scenario: Regional interface + Given I use the regional interface for "us-west-1" + When I ask for the list of EC2 availability zones + Then a request should have been made like: + | TYPE | NAME | VALUE | + | http | host | ec2.us-west-1.amazonaws.com | diff --git a/features/ec2/reserved_instances.feature b/features/ec2/reserved_instances.feature new file mode 100644 index 00000000000..e9b207ab1ef --- /dev/null +++ b/features/ec2/reserved_instances.feature @@ -0,0 +1,68 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @reserved_instances +Feature: EC2 Reserved Instances + + As a user of the high-level interface for EC2 + I want to describe and purchase reserved instance offerings + So that I can save money + + Scenario: Describing reserved instance offerings + When I enumerate reserved instance offerings + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeReservedInstancesOfferings | + + @memoized + Scenario: Describing reserved instance offerings with memoization + Given I start a memoization block + And I find the cheapest fixed price for a Linux/UNIX reserved instance + And I find the most expensive fixed price for a Linux/UNIX reserved instance + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeReservedInstancesOfferings | + + Scenario: Filtering reserved instances offerings + When I get a list of reserved instances offerings like: + """ + @ec2.reserved_instances_offerings.filter('instance-type', 'm1.small').to_a + """ + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeReservedInstancesOfferings | + | param | Filter.1.Value.1 | m1.small | + | param | Filter.1.Name | instance-type | + + Scenario: Purchasing a reserved instances offering + When I purchase a reserved instances offering + Then reserved instances should contain the returned reservation + + Scenario: Getting details about a reservation + Given I purchase a reserved instances offering + When I get the fixed price for the reservation + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeReservedInstances | + | param_match | ReservedInstancesId.1 | .* | + + @memoized + Scenario: Describing reserved instances with memoization + Given I purchase a reserved instances offering + And I start a memoization block + When I find the most expensive fixed price reservation in my account + And I count the reservations I have purchased + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeReservedInstances | diff --git a/features/ec2/security_groups.feature b/features/ec2/security_groups.feature new file mode 100644 index 00000000000..04e0392fdbb --- /dev/null +++ b/features/ec2/security_groups.feature @@ -0,0 +1,111 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @security_groups +Feature: EC2 Security Groups + + As a user of the high-level interface for EC2 + I want to create, modify and describe security groups + So that I can use EC2 securely + + Scenario: Create a security group + When I create a security group + Then The security group should be in the list + + Scenario: Delete a security group + Given I create a security group + When I delete the security group + Then The security group should not be in the list + + Scenario: Get a list of security groups + Given I create the following security groups: + | name | + | ruby-integration-test-1 | + | ruby-integration-test-2 | + | ruby-integration-test-3 | + When I get a list of security groups + Then The following security groups should be in the list + | name | + | ruby-integration-test-1 | + | ruby-integration-test-2 | + | ruby-integration-test-3 | + + @memoized + Scenario: List security groups with memoization + Given I create the following security groups: + | name | + | ruby-integration-test-1 | + | ruby-integration-test-2 | + | ruby-integration-test-3 | + And I start a memoization block + When I get a list of all authorized ingresses + And I compute a hash of security group name to description + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeSecurityGroups | + + Scenario: Fitlering security groups + Given I create the following security groups: + | name | + | ruby-integration-test-1 | + | ruby-integration-test-2 | + | ruby-integration-test-3 | + When I get a list of security groups filtered like: + """ + @ec2.security_groups.filter('group-name', '*test-2').to_a + """ + Then The following security groups should be in the list + | name | + | ruby-integration-test-2 | + Then The following security groups should not be in the list + | name | + | ruby-integration-test-1 | + | ruby-integration-test-3 | + + Scenario: Getting a single security group by name + Given I create the following security groups: + | name | description | + | ruby-integration-test-1 | short desc | + When I get the security group by name "ruby-integration-test-1" + Then The security group should have the description "short desc" + And The security group should have an owner id + + Scenario: authorizing ingress ip addresses + Given I create the following security groups: + | name | + | ruby-integration-test-1 | + | ruby-integration-test-2 | + And I get the security group by name "ruby-integration-test-1" + When I authorize "tcp" over port 22 for: + | type | value | + | ip_range | 127.0.0.1/0 | + | group | ruby-integration-test-2 | + Then The security group should allow "tcp" over port 80 for: + | type | value | + | ip_range | 127.0.0.1/0 | + | group | ruby-integration-test-2 | + + Scenario: Get security group by ID + When I get the security group "sg-123" by ID + Then the result should be an security group object + + Scenario: Check that an security group exists (does not exist) + Given I get the security group "sg-123" by ID + When I ask if the security group exists + Then the result should be false + + Scenario: Check that an security group exists (exists) + Given I create a security group + When I ask if the security group exists + Then the result should be true diff --git a/features/ec2/snapshot_attributes.feature b/features/ec2/snapshot_attributes.feature new file mode 100644 index 00000000000..e7addeac9ff --- /dev/null +++ b/features/ec2/snapshot_attributes.feature @@ -0,0 +1,76 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @snapshots +Feature: Snapshot Attributes + + As a user of the high-level interface for EC2 + I want to read, modify, and reset snapshot attributes + So that I can better manage my existing snapshots + + Scenario: Get simple snapshot attributes + Given I create a snapshot with description "foobar" + Then the snapshot volume ID should match the volume it was created from + And the snapshot status should be one of: + | pending | + | completed | + | error | + And the snapshot start time should be within a minute of now + And the snapshot progress should be between 0 and 100 + And the snapshot owner ID should be the account ID + And the snapshot volume size should be 1 + And the snapshot owner alias should be nil + And the snapshot description should be "foobar" + + Scenario: Get snapshot create volume permissions + Given I create a snapshot + Then the snapshot create volume permissions should be empty + And the snapshot should be private + + @memoized + Scenario: Memoized create volume permissions + Given I create a snapshot + When I add snapshot create volume permissions for the user "599169622985" + And I start a memoization block + And I get the snapshot create volume permissions + And I get the snapshot create volume permissions again + Then the snapshot create volume permissions should include: + | 599169622985 | + And exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeSnapshotAttribute | + | param | Attribute | createVolumePermission | + + @set + Scenario: Make snapshot public + Given I create a snapshot + When I make the snapshot public + Then the snapshot create volume permissions should be empty + And the snapshot should be public + + @set + Scenario: Remove explicit snapshot create volume permission + Given I create a snapshot + And I add snapshot create volume permissions for the user "599169622985" + And the snapshot create volume permissions should include: + | 599169622985 | + When I remove snapshot create volume permissions for the user "599169622985" + Then the snapshot create volume permissions should be empty + + @reset + Scenario: Reset snapshot create volume permissions + Given I create a snapshot + And I add snapshot create volume permissions for the user "599169622985" + When I reset the snapshot create volume permissions + Then the snapshot create volume permissions should be empty diff --git a/features/ec2/snapshots.feature b/features/ec2/snapshots.feature new file mode 100644 index 00000000000..8c29e034537 --- /dev/null +++ b/features/ec2/snapshots.feature @@ -0,0 +1,97 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @snapshots +Feature: Basic Snapshot Operations + + As a user of the high-level interface for EC2 + I want to create, list, and delete snapshots + So that I can use EBS. + + Scenario: Create Snapshot + Given I create a volume with the following parameters: + | parameter | value | + | size | 1 | + | availability_zone | us-east-1a | + When I create a snapshot from the volume + Then the result should be a snapshot + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | CreateSnapshot | + | param_match | VolumeId | vol-.+ | + + @memoized + Scenario: Memoized attributes from CreateSnapshot + Given I start a memoization block + And I create a snapshot + When I get the following attributes of the snapshot: + | volume | + | status | + | start_time | + | progress | + | owner_id | + | volume_size | + | description | + Then no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeSnapshots | + + Scenario: List snapshots + Given I create a volume with the following parameters: + | parameter | value | + | size | 1 | + | availability_zone | us-east-1a | + And I create a snapshot from the volume + When I ask for the list of snapshots + Then the snapshot I created should be in the list + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeSnapshots | + + @memoized + Scenario: List snapshots with memoization + Given I create a snapshot + And I start a memoization block + When I find the largest snapshot + And I get all snapshots grouped by owner ID + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeSnapshots | + + Scenario: Delete a snapshot + Given I create a volume with the following parameters: + | parameter | value | + | size | 1 | + | availability_zone | us-east-1a | + And I create a snapshot from the volume + When I delete the snapshot + Then the snapshot should eventually not exist + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DeleteSnapshot | + | param_match | SnapshotId | snap-.+ | + + Scenario: Check that a snapshot exists (does not exist) + Given I ask for the snapshot "snap-123" by ID + When I ask if the snapshot exists + Then the result should be false + + Scenario: Check that an snapshot exists (exists) + Given I create a volume with the following parameters: + | parameter | value | + | size | 1 | + | availability_zone | us-east-1a | + And I create a snapshot from the volume + When I ask if the snapshot exists + Then the result should be true diff --git a/features/ec2/step_definitions/combined.rb b/features/ec2/step_definitions/combined.rb new file mode 100644 index 00000000000..89d6c568fca --- /dev/null +++ b/features/ec2/step_definitions/combined.rb @@ -0,0 +1,42 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'net/ssh' + +When /^I add SSH access from my current IP to the security group$/ do + url = 'https://console.aws.amazon.com/checkip' + @my_ip = HTTParty.get(url).body.split(/"/)[1] + @security_group.authorize_ingress(:tcp, 22, "#{@my_ip}/0") +end + +When /^I should be able to ssh to the elastic ip as "([^\"]*)"$/ do |username| + +#puts "instance id: #{@instance.id}" +#puts "username: #{username}" +#puts "my_ip: #{@my_ip}" +#puts "security group: #{@security_group.name}" +#puts "elastic ip: #{@elastic_ip.ip_address}" +#puts "instance ip: #{@instance.ip_address}" +#puts "private key: #{@key_pair.private_key}" +#puts "instance status: #{@instance.status}" + + #sleep(60) + + ssh = Net::SSH.start(@instance.ip_address, username, + :key_data => [@key_pair.private_key], + :paranoid => false) + files = ssh.exec!("ls -la") + ssh.close + #puts files + +end diff --git a/features/ec2/step_definitions/ec2.rb b/features/ec2/step_definitions/ec2.rb new file mode 100644 index 00000000000..f92905cadb0 --- /dev/null +++ b/features/ec2/step_definitions/ec2.rb @@ -0,0 +1,101 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I get the following attributes of the (.*):$/ do |resource, table| + table.raw.flatten.each do |att| + # p att + instance_variable_get("@"+resource.tr(" ", "_")).send(att) + end +end + +Before("@ec2") do + + @ec2 = EC2.new + @ec2_client = @ec2.client + @created_key_pairs = [] + @created_security_groups = [] + @allocated_elastic_ips = [] + @started_instances = [] + @tags_created = [] + @created_images = [] + @created_volumes = [] + @created_snapshots = [] + + @test_config["account_id"] ||= + @ec2.security_groups.filter("group-name", "default").first.owner_id + +end + +After("@ec2") do + + @tags_created.each do |tag| + begin + tag.delete + rescue + end + end + + @started_instances.each do |id| + begin + EC2::Instance.new(id).api_termination_disabled = false + rescue + end + end + + unless @started_instances.empty? + @ec2_client.terminate_instances(:instance_ids => @started_instances) + end + + @allocated_elastic_ips.each do |elastic_ip| + begin + elastic_ip.release + rescue + end + end + + @created_key_pairs.each do |key_pair| + begin + key_pair.delete + rescue + end + end + + @created_security_groups.each{|group| + begin + group.delete + rescue + end + } + + @created_images.each do |image| + begin + image.delete + rescue + end + end + + @created_volumes.each do |volume| + begin + volume.delete + rescue + end + end + + @created_snapshots.each do |snapshot| + begin + snapshot.delete + rescue + end + end + +end diff --git a/features/ec2/step_definitions/elasic_ips.rb b/features/ec2/step_definitions/elasic_ips.rb new file mode 100644 index 00000000000..087cb4aa571 --- /dev/null +++ b/features/ec2/step_definitions/elasic_ips.rb @@ -0,0 +1,54 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I allocate an elastic ip$/ do + @elastic_ip = @ec2.elastic_ips.allocate + @allocated_elastic_ips << @elastic_ip +end + +When /^I list allocated elastic ips$/ do + @elastic_ips = @ec2.elastic_ips.to_a +end + +Then /^The list should contain the elastic ip$/ do + @elastic_ips.should include(@elastic_ip) +end + +When /^I release the elastic ip address$/ do + @elastic_ip.release +end + +Then /^The list should not contain the elastic ip$/ do + @elastic_ips.should_not include(@elastic_ip) +end + +When /^I associate the elastic ip with the instance$/ do + @instance.associate_elastic_ip(@elastic_ip) +end + +Then /^The instance should eventually have the new elastic ip$/ do + eventually do + @instance.ip_address.should == @elastic_ip.ip_address + end +end + +When /^I compute a map of public IP address to instance ID$/ do + @ec2.elastic_ips.inject({}) do |hash, ip| + hash[ip.ip_address] = ip.instance_id + hash + end +end + +When /^I count the elastic IPs in my account$/ do + @ec2.elastic_ips.inject(0) { |count, ip| count + 1 } +end diff --git a/features/ec2/step_definitions/errors.rb b/features/ec2/step_definitions/errors.rb new file mode 100644 index 00000000000..f987b3f6e7f --- /dev/null +++ b/features/ec2/step_definitions/errors.rb @@ -0,0 +1,26 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^(.*) rescuing "([^\"]*)"$/ do |i_do_something, class_name| + error = AWS.module_eval(class_name) + begin + When i_do_something + rescue error => e + @error = e + end +end + +Then /^I should rescue the error with code "([^\"]*)"$/ do |code| + @error.should_not be_nil + @error.code.should == code +end diff --git a/features/ec2/step_definitions/image_attributes.rb b/features/ec2/step_definitions/image_attributes.rb new file mode 100644 index 00000000000..1f90406155b --- /dev/null +++ b/features/ec2/step_definitions/image_attributes.rb @@ -0,0 +1,108 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Then /^the image owner ID should eventually be the account ID$/ do + @image.owner_id.should == @test_config["account_id"] +end + +When /^I ask if the image is public$/ do + @result = @image.public? +end + +Then /^the image (.*) should eventually be "([^\"]*)"$/ do |field, value| + field = field.downcase.gsub(/\s+/,'_') + @image.send(field).should == value +end + +Then /^the image (.*) should eventually be nil$/ do |field| + field = field.downcase.gsub(/\s+/,'_') + @image.send(field).should be_nil +end + +Then /^the image (.*) should eventually be :(\w+)$/ do |field, value| + field = field.downcase.gsub(/\s+/,'_') + @image.send(field).should == value.to_sym +end + +Given /^I create an image from an invalid snapshot ID$/ do + pending +# @ec2.images.create(:name => "my-image", +# :root_device_name => "/dev/sda1", +# :block_device_mappings => { +# "/dev/sda1" => { +# :snapshot_id => "snap-deadbeef" +# } +# }) +end + +Then /^the image state change reasons should eventually include:$/ do |table| + pending + table.hashes.each do |h| + @image.state_change_reasons.map { |scr| src.code }. + should include(h["code"]) + scr = @image.state_change_reasons.find { |scr| scr.code == h["code"] } + scr.message.should == h["message"] + end +end + +When /^the image block device mappings should eventually have the following mappings:$/ do |table| + eventually do + table.hashes.each do |h| + mappings = @image.block_device_mappings + mappings.should have_key(h["device name"]) + mapping = mappings[h["device name"]] + mapping.delete_on_termination?.to_s.should == h["delete on termination"] + end + end +end + +Then /^the image launch permissions should eventually be empty$/ do + @image.launch_permissions.to_a.should be_empty +end + +Then /^the image should be public$/ do + @image.public?.should be_true +end + +When /^I make the image public$/ do + @image.public = true +end + +When /^I add image launch permissions for the user "([^\"]*)"$/ do |user_id| + @image.launch_permissions.add(user_id) +end + +Then /^the image launch permissions should eventually include:$/ do |table| + eventually do + permissions = @image.launch_permissions.to_a + table.raw.each do |user_id| + permissions.should include(user_id.flatten.first) + end + end +end + +When /^I remove image launch permissions for the user "([^\"]*)"$/ do |user_id| + @image.launch_permissions.remove(user_id) +end + +When /^I set the image description to "([^\"]*)"$/ do |description| + @image.description = description +end + +When /^I reset the image launch permissions$/ do + @image.launch_permissions.reset +end + +When /^I get the image launch permissions$/ do + @image.launch_permissions.to_a +end diff --git a/features/ec2/step_definitions/images.rb b/features/ec2/step_definitions/images.rb new file mode 100644 index 00000000000..afa6c9b89fa --- /dev/null +++ b/features/ec2/step_definitions/images.rb @@ -0,0 +1,103 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Before("@images") do + @ec2.images.with_owner("self").filter("name", "my-image").each do |i| + i.delete + end +end + +When /^I create an image( from the instance)? with the following parameters:$/ do |from_instance, table| + opts = table.hashes.inject({}) do |opts, h| + opts[h["parameter"].to_sym] = + case h["parameter"] + when "no_reboot" then h["value"] == "true" + else + h["value"] + end + opts + end + opts[:instance_id] = @instance.id unless from_instance.to_s.empty? + @image = + @ec2.images.create(opts) + @created_images << @image +end + +Then /^the image should eventually be in the list of images I own$/ do + eventually do + @ec2.images.with_owner("self").to_a. + should include(@image) + end +end + +Then /^the result should be an image$/ do + @image.should be_an(EC2::Image) +end + +When /^I ask for the list of images owned by me$/ do + @result = @ec2.images.with_owner("self").to_a +end + +Then /^the image I created should be in the list$/ do + @result.should include(@image) +end + +When /^I ask for the image "([^\"]*)" by ID$/ do |id| + @image = @result = @ec2.images[id] +end + +When /^I ask if the image exists$/ do + @result = @image.exists? +end + +When /^I deregister the image$/ do + @image.deregister +end + +Then /^the image should eventually not exist$/ do + eventually { @image.exists?.should be_false } +end + +Given /^I wait for the image to exist$/ do + eventually { @image.exists?.should be_true } +end + +Given /^I create an image with the following block device mappings:$/ do |string| + mappings = eval(string) + @result = @image = @ec2.images.create(:name => "my-image", + :root_device_name => "/dev/sda1", + :block_device_mappings => mappings) + @created_images << @image +end + +When /^I compute a hash of image name to image ID for Amazon\-owned images$/ do + @ec2.images.filter("owner-alias", "amazon").inject({}) do |hash, image| + hash[image.name] = image.id + hash + end +end + +When /^I sort the list of Amazon\-owned images by image location$/ do + @ec2.images.filter("owner-alias", "amazon").sort_by(&:location) +end + +When /^I compute a hash of mapped snapshot ID to image ID for Amazon\-owned images$/ do + @ec2.images.filter("owner-alias", "amazon").inject({}) do |hash, image| + image.block_device_mappings.each do |device, mapping| + if mapping.respond_to?(:snapshot_id) + hash[mapping.snapshot_id] = image + end + end + hash + end +end diff --git a/features/ec2/step_definitions/instance_attributes.rb b/features/ec2/step_definitions/instance_attributes.rb new file mode 100644 index 00000000000..5a8b36fbd4d --- /dev/null +++ b/features/ec2/step_definitions/instance_attributes.rb @@ -0,0 +1,207 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I get the instance\'s user data$/ do + @result = @instance.user_data +end + +When /^I get the instance type$/ do + @result = @instance.instance_type +end + +When /^I ask if termination is disabled$/ do + @result = @instance.api_termination_disabled? +end + +Then /^the result should (eventually )?be (false|true)$/ do |eventual, truthiness| + l = lambda { @result.should send("be_#{truthiness}") } + if eventual == "eventually" + eventually(&l) + else + l.call + end +end + +When /^I disable API termination for the instance$/ do + @instance.api_termination_disabled = true +end + +Then /^the instance should eventually have API termination disabled$/ do + eventually { @instance.api_termination_disabled?.should be_true } +end + +When /^I set the instance\'s user data to "([^\"]*)"$/ do |data| + @instance.user_data = data +end + +Then /^the instance\'s user data should eventually be "([^\"]*)"$/ do |data| + eventually { @instance.user_data.should == data } +end + +When /^I change the instance type to "([^\"]*)"$/ do |type| + @instance.instance_type = type +end + +Then /^the instance type should eventually be "([^\"]*)"$/ do |type| + eventually { @instance.instance_type.should == type } +end + +When /^I ask if source\/destination checking is enabled$/ do + pending # VPC + @result = @instance.source_dest_check? +end + +When /^I disable source\/destination checking$/ do + pending # VPC + @instance.source_dest_check = false +end + +Then /^the instance should eventually have source\/destination checking disabled$/ do + eventually { @instance.source_dest_check?.should be_false } +end + +When /^I get the instance\-initiated shutdown behavior$/ do + @result = @instance.instance_initiated_shutdown_behavior +end + +When /^I set the instance\-initiated shutdown behavior to "([^\"]*)"$/ do |behavior| + @instance.instance_initiated_shutdown_behavior = behavior +end + +Then /^the instance\-initiated shutdown behavior should eventually be "([^\"]*)"$/ do |behavior| + eventually { @instance.instance_initiated_shutdown_behavior.should == behavior } +end + +When /^I get the instance\'s (kernel|ramdisk) ID$/ do |kind| + @result = @instance.send("#{kind}_id") +end + +When /^I set the instance\'s (kernel|ramdisk) ID to "([^\"]*)"$/ do |kind, id| + @instance.send("#{kind}_id=", id) +end + +Then /^the instance\'s (kernel|ramdisk) ID should eventually be "([^\"]*)"$/ do |kind, id| + eventually { @instance.send("#{kind}_id").should == id } +end + +Given /^I put the instance into a stopped state$/ do + Given %(I wait for the instance status to be "running") + Given %(I stop the instance) + Given %(I wait for the instance status to be "stopped") +end + +When /^I get the instance\'s root device name$/ do + @result = @instance.root_device_name +end + +When /^I get the instance\'s block device mappings$/ do + @result = @instance.block_device_mappings +end + +Then /^the result should have the following mappings:$/ do |table| + table.hashes.each do |h| + @result.should have_key(h["device name"]) + mapping = @result[h["device name"]] + mapping.delete_on_termination?.to_s.should == h["delete on termination"] + end +end + +When /^I query the attributes of the instance$/ do + # sugar +end + +Then /^I get values like the following from the instance:$/ do |table| + table.rows_hash.each do |attribute, value_pattern| + @instance.send(attribute).to_s.should match(Regexp.compile(value_pattern)) + end +end + +When /^I ask if monitoring is enabled$/ do + @result = @instance.monitoring_enabled? +end + +When /^I (enable|disable) monitoring on the instance$/ do |action| + @instance.monitoring_enabled = (action == "enable") +end + +Then /^the instance should eventually have monitoring (enabled|disabled)$/ do |status| + eventually do + @instance.monitoring_enabled?.should send("be_#{status == 'enabled'}") + end +end + +When /^I get the instance\'s availability zone$/ do + @result = @instance.availability_zone +end + +When /^I get the instance\'s state transition reason$/ do + @result = @instance.state_transition_reason +end + +Then /^the result should match "([^\"]*)"$/ do |pattern| + @result.should match(Regexp.compile(pattern)) +end + +When /^I get the instance\'s launch time$/ do + @result = @instance.launch_time +end + +Then /^the result should be a time within the last hour$/ do + @result.should be_a(Time) + @result.should be_within(60*60).of(Time.now) +end + +When /^I get the instance\'s platform$/ do + @result = @instance.platform +end + +When /^I reset the instance\'s kernel ID$/ do + @instance.reset_kernel_id +end + +When /^I reset the instance\'s ramdisk ID$/ do + @instance.reset_ramdisk_id +end + +When /^I ask for the security groups associated with the instance$/ do + @security_groups = @result = @instance.security_groups +end + +Then /^the result should be an array$/ do + @result.should be_an(Array) +end + +When /^I get the instance\'s image ID$/ do + @result = @instance.image_id +end + +Then /^the instance key pair should be the one I created it with$/ do + @instance.key_pair.should == @key_pair +end + +Given /^I request to run an instance from a windows image$/ do + @instance = @ec2.images. + filter("is-public", "true"). + filter("platform", "windows"). + filter("manifest-location", "*English-Base*"). + sort_by { |i| i.location }.last. + run_instance(:instance_type => "t1.micro") +end + +When /^I list instances filtering by the instance ID I just created$/ do + @result = @ec2.instances.filter("instance-id", @instance.id) +end + +When /^I get the instance type of the first item in the result$/ do + @result = @result.first.instance_type +end diff --git a/features/ec2/step_definitions/instances.rb b/features/ec2/step_definitions/instances.rb new file mode 100644 index 00000000000..c3e1d2974e5 --- /dev/null +++ b/features/ec2/step_definitions/instances.rb @@ -0,0 +1,130 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I request to run an instance with the following parameters:$/ do |table| + opts = {} + table.hashes.each do |h| + opts[h["parameter"].to_sym] = + case h["parameter"] + when "disable_api_termination", "monitoring_enabled" + h["value"] == "true" + else + h["value"] + end + end + + if opts[:security_groups] + opts[:security_groups] = [opts[:security_groups]] + end + + @instance = @result = @ec2.instances.create(opts) + @started_instances << @instance.id + sleep 0.1 +end + +When /^I request to run between (\d+) and (\d+) instances with the following parameters:$/ do |min, max, table| + opts = {} + table.hashes.each do |h| + opts[h["parameter"].to_sym] = h["value"] + end + @instances = @result = + @ec2.instances.create(opts.merge(:count => (min.to_i)..(max.to_i))) + @started_instances += @instances.map { |i| i.id } +end + +When /^I request to run an instance of "([^\"]*)" with the following block device mappings:$/ do |ami_id, string| + mappings = eval(string) + @instance = @result = @ec2.instances.create(:image_id => ami_id, + :block_device_mappings => mappings) + @started_instances << @instance.id +end + +Given /^I request to run an instance of "([^\"]*)" using the key pair$/ do |image_id| + @instance = @result = @ec2.instances.create(:image_id => image_id, + :key_pair => @key_pair) + @started_instances << @instance.id +end + +Then /^The result should be an instance object$/ do + @result.should be_an(EC2::Instance) +end + +Then /^The result should be an array of instance objects$/ do + @result.should be_an(Array) + @result.each { |i| i.should be_an(EC2::Instance) } +end + +When /^I terminate the instance$/ do + @instance.terminate +end + +Then /^the instance status should eventually be "([^\"]*)"$/ do |status| + eventually do + @instance.status.should == status.tr("-","_").to_sym + end +end + +When /^I ask for the list of instances$/ do + @result = @ec2.instances.to_a +end + +Then /^the instance I started should be in the list$/ do + @result.should include(@instance) +end + +Given /^I wait for the instance status to be "([^\"]*)"$/ do |status| + Given %(the instance status should eventually be "#{status}") +end + +When /^I reboot the instance$/ do + @instance.reboot +end + +When /^I stop the instance$/ do + @instance.stop +end + +When /^I start the instance$/ do + @instance.start +end + +When /^I ask for the instance "([^\"]*)" by ID$/ do |id| + @instance = @result = @ec2.instances[id] +end + +Then /^the result should be an instance object$/ do + @result.should be_an(EC2::Instance) +end + +When /^I ask if the instance exists$/ do + @result = @instance.exists? +end + +Given /^I wait for the instance to exist$/ do + eventually { @instance.should exist } +end + +When /^I compute a hash of instance IDs mapped to instance status$/ do + @ec2.instances.inject({}) do |hash, instance| + hash[instance.id] = instance.status + hash + end +end + +When /^I get a list of instances sorted by launch time$/ do + @ec2.instances.sort_by(&:launch_time) +end + +When /^I get the instance status$/ do + @result = @instance.status +end diff --git a/features/ec2/step_definitions/key_pairs.rb b/features/ec2/step_definitions/key_pairs.rb new file mode 100644 index 00000000000..92f6f3050ee --- /dev/null +++ b/features/ec2/step_definitions/key_pairs.rb @@ -0,0 +1,74 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I create a key pair$/ do + name = "ruby-integration-text-#{Time.now.to_i}" + When "I create a key pair named \"#{name}\"" +end + +When /^I create a key pair named "([^\"]*)"$/ do |name| + @key_pair = @ec2.key_pairs.create(name) + @created_key_pairs << @key_pair +end + +When /^I list key pairs$/ do + @key_pairs = @ec2.key_pairs.to_a +end + +Then /^The list should contain the key pair$/ do + @key_pairs.should include(@key_pair) +end + +When /^I delete the key pair$/ do + @key_pair.delete +end + +Then /^The list should not contain the key pair$/ do + @key_pairs.should_not include(@key_pair) +end + +Given /^I import a key pair named "([^\"]*)" with the public key:$/ do |name, public_key| + @key_pair = @ec2.key_pairs.import(name, public_key.strip) + @created_key_pairs << @key_pair +end + +When /^I get the key pair named "([^\"]*)"$/ do |name| + @result = @key_pair = @ec2.key_pairs[name] +end + +Then /^It should have the fingerprint:$/ do |fingerprint| + @key_pair.fingerprint.should == fingerprint.strip +end + +Then /^the result should be an key pair object$/ do + @result.should be_an(EC2::KeyPair) +end + +When /^I ask if the key pair exists$/ do + @result = @key_pair.exists? +end + +When /^I compute a map of key name to key fingerprint$/ do + @ec2.key_pairs.inject({}) do |hash, key_pair| + hash[key_pair.name] = key_pair.fingerprint + hash + end +end + +When /^I count the key pairs in my account$/ do + @ec2.key_pairs.inject(0) { |count, key_pair| count + 1 } +end + +When /^I get the key pair fingerprint$/ do + @result = @key_pair.fingerprint +end diff --git a/features/ec2/step_definitions/regions.rb b/features/ec2/step_definitions/regions.rb new file mode 100644 index 00000000000..54e24a11dee --- /dev/null +++ b/features/ec2/step_definitions/regions.rb @@ -0,0 +1,51 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I ask for the list of EC2 regions$/ do + @result = @ec2.regions.to_a +end + +Then /^the result should contain the following region information:$/ do |table| + table.hashes.each do |h| + @result.map { |r| r.name }.should include h["name"] + @result.map { |r| r.endpoint }.should include h["endpoint"] + end +end + +Given /^I use the regional interface for "([^\"]*)"$/ do |region| + @orig_ec2 ||= @ec2 + @ec2 = @ec2.regions[region] +end + +Given /^I ask for the region "([^\"]*)" by name$/ do |name| + @region = @ec2.regions[name] +end + +When /^I ask if the region exists$/ do + @result = @region.exists? +end + +Given /^I ask for the first region$/ do + @region = @ec2.regions.first +end + +When /^I compute a hash of region name to region endpoint$/ do + @ec2.regions.inject({}) do |hash, region| + hash[region.name] = region.endpoint + hash + end +end + +When /^I count the regions available to my account$/ do + @ec2.regions.inject(0) { |count, region| count + 1 } +end diff --git a/features/ec2/step_definitions/reserved_instances.rb b/features/ec2/step_definitions/reserved_instances.rb new file mode 100644 index 00000000000..0ea75b7f869 --- /dev/null +++ b/features/ec2/step_definitions/reserved_instances.rb @@ -0,0 +1,51 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I enumerate reserved instance offerings$/ do + @offerings = @ec2.reserved_instances_offerings.to_a +end + +When /^I get a list of reserved instances offerings like:$/ do |expression| + @offerings = eval(expression) +end + +When /^I purchase a reserved instances offering$/ do + offering_id = @test_config['reserved_instances_offering_id'] + @reservation = @ec2.reserved_instances_offerings[offering_id].purchase +end + +Then /^reserved instances should contain the returned reservation$/ do + @ec2.reserved_instances.should include(@reservation) +end + +When /^I get the fixed price for the reservation$/ do + @fixed_price = @ec2.reserved_instances[@reservation.id].fixed_price +end + +Given /^I find the cheapest fixed price for a Linux\/UNIX reserved instance$/ do + @ec2.reserved_instances_offerings.filter("product-description", "Linux/UNIX"). + map { |offering| offering.fixed_price }.min +end + +Given /^I find the most expensive fixed price for a Linux\/UNIX reserved instance$/ do + @ec2.reserved_instances_offerings.filter("product-description", "Linux/UNIX"). + map { |offering| offering.fixed_price }.max +end + +When /^I find the most expensive fixed price reservation in my account$/ do + @ec2.reserved_instances.map { |ri| ri.fixed_price }.max +end + +When /^I count the reservations I have purchased$/ do + @ec2.reserved_instances.inject(0) { |count, ri| count + 1 } +end diff --git a/features/ec2/step_definitions/security_groups.rb b/features/ec2/step_definitions/security_groups.rb new file mode 100644 index 00000000000..3852d066925 --- /dev/null +++ b/features/ec2/step_definitions/security_groups.rb @@ -0,0 +1,128 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Given /^I create a security group$/ do + name = "ruby-integration-test-#{Time.now.to_i}" + Given "I create a security group named \"#{name}\"" +end + +Given /^I create a security group named "([^\"]*)"$/ do |name| + @security_group = @ec2.security_groups.create(name) + @created_security_groups << @security_group +end + +Then /^I delete the security group$/ do + @security_group.delete +end + +Then /^The security group should be in the list$/ do + @ec2.security_groups.should include(@security_group) +end + +Then /^The security group should not be in the list$/ do + @ec2.security_groups.should_not include(@security_group) +end + +Given /^I create the following security groups:$/ do |table| + table.hashes.each do |row| + options = {} + options[:description] = row['description'] if row['description'] + security_group = @ec2.security_groups.create(row['name'], options) + @created_security_groups << security_group + end +end + +When /^I get a list of security groups$/ do + @security_groups = @ec2.security_groups.to_a +end + +Then /^The following security groups should be in the list$/ do |table| + names = table.hashes.collect{|row| row['name'] } + names.all? do |name| + @security_groups.collect{|sg| sg.name }.include?(name) + end +end + +When /^I get a list of security groups filtered like:$/ do |expression| + @security_groups = eval(expression) +end + +Then /^The following security groups should not be in the list$/ do |table| + names = table.hashes.collect{|row| row['name'] } + names.none? do |name| + @security_groups.collect{|sg| sg.name }.include?(name) + end +end + +When /^I get the security group by name "([^\"]*)"$/ do |name| + @result = @security_group = @ec2.security_groups.filter('group-name', name).first +end + +Then /^The security group should have the description "([^\"]*)"$/ do |desc| + @security_group.description.should == desc +end + +Then /^The security group should have an owner id$/ do + @security_group.owner_id.should_not be_nil +end + +def ip_ranges_from_table table + table.hashes.select{|r| r['type'] == 'ip_range'}.collect{|r| r['value'] } +end + +def security_groups_from_table table, owner_id + names = table.hashes.select{|r| r['type'] == 'group'}.collect{|r| r['value'] } + @ec2.security_groups.filter('group-name', *names).to_a +end + +When /^I authorize "([^\"]*)" over port (\d+) for:$/ do |protocol, port, table| + ip_ranges = ip_ranges_from_table(table) + groups = security_groups_from_table(table, @security_group.owner_id) + @security_group.authorize_ingress(protocol, port, *(ip_ranges + groups)) +end + +Then /^The security group should allow "([^\"]*)" over port (\d+) for:$/ do |protocol, port, table| + + permissions = @security_group.ip_permissions + ip_ranges = ip_ranges_from_table(table) + groups = security_groups_from_table(table, @security_group.owner_id) + permissions.any? do |p| + p.protocol.to_s == protocol and + p.port_range.first.to_s == port and + p.ip_ranges == ip_ranges and + p.groups == groups + end +end + +Then /^the result should be an security group object$/ do + @result.should be_an(EC2::SecurityGroup) +end + +When /^I ask if the security group exists$/ do + @result = @security_group.exists? +end + +When /^I get the security group "([^\"]*)" by ID$/ do |id| + @result = @security_group = @ec2.security_groups[id] +end + +When /^I get a list of all authorized ingresses$/ do + @ec2.security_groups.map(&:ip_permissions).flatten +end + +When /^I compute a hash of security group name to description$/ do + @ec2.security_groups.inject({}) do |hash, sg| + hash[sg.name] = sg.description + hash + end +end diff --git a/features/ec2/step_definitions/snapshot_attributes.rb b/features/ec2/step_definitions/snapshot_attributes.rb new file mode 100644 index 00000000000..64a3d568639 --- /dev/null +++ b/features/ec2/step_definitions/snapshot_attributes.rb @@ -0,0 +1,89 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Then /^the snapshot volume ID should match the volume it was created from$/ do + @snapshot.volume_id.should == @volume.id +end + +Then /^the snapshot status should be one of:$/ do |table| + table.raw.flatten.map { |status| status.to_sym }.should include(@snapshot.status) +end + +Then /^the snapshot start time should be within a minute of now$/ do + @snapshot.start_time.should be_within(60).of(Time.now) +end + +Then /^the snapshot progress should be between (\d+) and (\d+)$/ do |low, high| + @snapshot.progress.should be_an(Integer) + ((low.to_i)..(high.to_i)).should include(@snapshot.progress) +end + +Then /^the snapshot owner ID should be the account ID$/ do + @snapshot.owner_id.should == @test_config["account_id"] +end + +Then /^the snapshot volume size should be (\d+)$/ do |size| + @snapshot.volume_size.should == size.to_i +end + +Then /^the snapshot owner alias should be nil$/ do + @snapshot.owner_alias.should be_nil +end + +Given /^I create a snapshot with description "([^\"]*)"$/ do |description| + Given %(I create a volume) + @snapshot = @volume.create_snapshot(description) + @created_snapshots << @snapshot +end + +Then /^the snapshot description should be "([^\"]*)"$/ do |description| + @snapshot.description.should == description +end + +Then /^the snapshot create volume permissions should be empty$/ do + @snapshot.create_volume_permissions.should be_empty +end + +Then /^the snapshot should be private$/ do + @snapshot.should be_private +end + +When /^I make the snapshot public$/ do + @snapshot.public = true +end + +Then /^the snapshot should be public$/ do + @snapshot.should be_public +end + +Given /^I add snapshot create volume permissions for the user "([^\"]*)"$/ do |user_id| + @snapshot.permissions.add user_id +end + +Given /^the snapshot create volume permissions should include:$/ do |table| + table.rows.flatten.each do |user_id| + @snapshot.permissions.should include(user_id) + end +end + +When /^I remove snapshot create volume permissions for the user "([^\"]*)"$/ do |user_id| + @snapshot.permissions.remove user_id +end + +When /^I reset the snapshot create volume permissions$/ do + @snapshot.permissions.reset +end + +When /^I get the snapshot create volume permissions$/ do + @snapshot.permissions.to_a +end diff --git a/features/ec2/step_definitions/snapshots.rb b/features/ec2/step_definitions/snapshots.rb new file mode 100644 index 00000000000..c09c064f368 --- /dev/null +++ b/features/ec2/step_definitions/snapshots.rb @@ -0,0 +1,62 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I create a snapshot from the volume$/ do + @snapshot = @result = @volume.create_snapshot + @created_snapshots << @snapshot +end + +Then /^the result should be a snapshot$/ do + @result.should be_an(EC2::Snapshot) +end + +When /^I ask for the list of snapshots$/ do + @result = @ec2.snapshots.to_a +end + +Then /^the snapshot I created should be in the list$/ do + @result.should include(@snapshot) +end + +When /^I delete the snapshot$/ do + @snapshot.delete +end + +Then /^the snapshot should eventually not exist$/ do + eventually { @snapshot.exists?.should be_false } +end + +Given /^I ask for the snapshot "([^\"]*)" by ID$/ do |id| + @snapshot = @result = @ec2.snapshots[id] +end + +When /^I ask if the snapshot exists$/ do + @result = @snapshot.exists? +end + +Given /^I create a snapshot$/ do + Given %(I create a volume) + @snapshot = @volume.create_snapshot + @created_snapshots << @snapshot +end + +When /^I find the largest snapshot$/ do + @ec2.snapshots.map { |snapshot| snapshot.volume_size }.max +end + +When /^I get all snapshots grouped by owner ID$/ do + @ec2.snapshots.inject({}) do |hash, snapshot| + (hash[snapshot.owner_id] ||= []) << snapshot + hash + end +end diff --git a/features/ec2/step_definitions/tagging.rb b/features/ec2/step_definitions/tagging.rb new file mode 100644 index 00000000000..8f3456d282b --- /dev/null +++ b/features/ec2/step_definitions/tagging.rb @@ -0,0 +1,111 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Given /^I call the "([^\"]*)" resource$/ do |var| + @resource = instance_variable_get("@#{var}") + @collection = @ec2.send("#{var}s") +end + +When /^I tag the resource "([^\"]*)"$/ do |tag_key| + @resource.tags << tag_key + @tag = EC2::Tag.new(@resource, tag_key) + @tags_created << @tag +end + +Then /^I should find the resource by its tag$/ do + @collection.tagged(@tag.key).should include(@resource) +end + +When /^I tag the resource "([^\"]*)" with the value "([^\"]*)"$/ do |key, value| + @resource.tags[key] = value + @tag = EC2::Tag.new(@resource, key) + @tags_created << @tag +end + +Then /^the resource tag "([^\"]*)" should eventually have value "([^\"]*)"$/ do |key, value| + eventually { @resource.tags[key].should == value } +end + +Then /^I(?: should)? find the resource by "([^\"]*)" and "([^\"]*)"$/ do |key, value| + @collection.tagged(key).tagged_values(value).should include(@resource) +end + +Then /^I delete the resource tag "([^\"]*)"$/ do |key| + @resource.tags.delete(key) +end + +Then /^I clear the resource tags$/ do + @resource.tags.clear +end + +Then /^the resource tag "([^\"]*)" should eventually have no value$/ do |key| + eventually { @resource.tags[key].should be_nil } +end + +Then /^the resource should eventually have no tags$/ do + eventually { @resource.tags.should be_empty } +end + +Then /^I should find the resource through the tags collection by "([^\"]*)" and "([^\"]*)"$/ do |key, value| + tags_with_key = @ec2.tags.filter("key", key) + tags_with_key.map { |t| t.value }.should include(value) + tags_with_value = tags_with_key.filter("value", value) + tags_with_value.map { |t| t.resource }.should include(@resource) +end + +Then /^the resource tag collection should include "([^\"]*)" with value "([^\"]*)"$/ do |key, value| + @resource.tags.should include(key) + @resource.tags[key].should == value +end + +Then /^the resource should be taggable$/ do + eventually { @resource.exists?.should be_true } + When 'I tag the resource "ruby-test-1"' + Then 'I should find the resource by its tag' + + When 'I tag the resource "ruby-test-2-key" with the value "ruby-test-2-value"' + Then 'the resource tag "ruby-test-2-key" should eventually have value "ruby-test-2-value"' + Then 'I should find the resource by "ruby-test-2-key" and "ruby-test-2-value"' + Then 'I should find the resource through the tags collection by "ruby-test-2-key" and "ruby-test-2-value"' + + When 'I delete the resource tag "ruby-test-2-key"' + Then 'the resource tag "ruby-test-2-key" should eventually have no value' + When 'I clear the resource tags' + Then 'the resource should eventually have no tags' +end + +Then /^the resource should memoize tags properly$/ do + eventually { @resource.exists?.should be_true } + When 'I tag the resource "ruby-test-1-key" with the value "ruby-test-1-value"' + And 'the resource tag "ruby-test-1-key" should eventually have value "ruby-test-1-value"' + And 'I start a memoization block' + And 'I find the resource by "ruby-test-1-key" and "ruby-test-1-value"' + Then 'the resource tag collection should include "ruby-test-1-key" with value "ruby-test-1-value"' + And('no requests should have been made like:', + table([%w(TYPE NAME VALUE), + %w(param Action DescribeTags)])) +end + +When /^I compute the set of all tag values$/ do + require 'set' + @ec2.tags.inject(Set.new) do |set, tag| + set << tag.value + end +end + +When /^I list all tagged resources grouped by the tag values of "([^\"]*)"$/ do |key| + @ec2.tags.inject({}) do |hash, tag| + hash[(tag.value if tag.key == key)] = tag.resource + hash + end +end diff --git a/features/ec2/step_definitions/volume_attributes.rb b/features/ec2/step_definitions/volume_attributes.rb new file mode 100644 index 00000000000..69e1aeabaf5 --- /dev/null +++ b/features/ec2/step_definitions/volume_attributes.rb @@ -0,0 +1,30 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Then /^the volume size should be (\d+)$/ do |size| + @volume.size.should == size.to_i +end + +Then /^the volume availability zone should be "([^\"]*)"$/ do |zone| + sleep 1 + @volume.availability_zone.to_s.should == zone +end + +Then /^the volume create time should be within an hour of now$/ do + @volume.create_time.should be_a(Time) + @volume.create_time.should be_within(60).of(Time.now) +end + +Then /^the volume snapshot should match the snapshot it was created from$/ do + @volume.snapshot.should == @snapshot +end diff --git a/features/ec2/step_definitions/volumes.rb b/features/ec2/step_definitions/volumes.rb new file mode 100644 index 00000000000..e318657c57d --- /dev/null +++ b/features/ec2/step_definitions/volumes.rb @@ -0,0 +1,134 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +After("@attachment") do |scenario| + @volume.attachments.each do |att| + att.delete(:force => true) rescue nil + end + eventually do + @volume.status.should == :available + end +end + +When /^I create a volume with the following parameters:$/ do |table| + opts = table.hashes.inject({}) do |opts, h| + opts[h["parameter"].to_sym] = + case h["parameter"] + when "size" then h["value"].to_i + else + h["value"] + end + opts + end + @volume = @result = @ec2.volumes.create(opts) + @created_volumes << @volume +end + +Given /^I create a volume$/ do + @volume = @ec2.volumes.create(:size => 1, + :availability_zone => @ec2.availability_zones.first) + @created_volumes << @volume +end + +When /^I create a volume from the snapshot$/ do + @volume = @result = @snapshot.create_volume(@ec2.availability_zones.first) + @created_volumes << @volume +end + +Then /^the result should be a volume$/ do + @result.should be_an(EC2::Volume) +end + +When /^I ask for the list of volumes$/ do + @result = @ec2.volumes.to_a +end + +Then /^the volume I created should be in the list$/ do + @result.should include(@volume) +end + +When /^I delete the volume$/ do + @volume.delete +end + +Then /^the volume state should eventually be "([^\"]*)"$/ do |state| + @volume.state.should == state.to_sym +end + +When /^I ask for the volume "([^\"]*)" by ID$/ do |id| + @result = @volume = @ec2.volumes[id] +end + +When /^I ask if the volume exists$/ do + @result = @volume.exists? +end + +Then /^the volume should( not)? have an attachment to the instance$/ do |should_not| + should = (should_not == " not" ? :should_not : :should) + @volume.attachments.map { |a| a.instance }.send(should, include(@instance)) +end + +Given /^I create a (\d+)GiB volume in the same availability zone as the instance$/ do |size| + @result = @volume = @ec2.volumes.create(:size => size.to_i, + :availability_zone => + @instance.availability_zone) + @created_volumes << @volume +end + +When /^I attach the volume to the instance as device "([^\"]*)"$/ do |device| + @attachment = @result = @volume.attach_to(@instance, device) +end + +Then /^the result should be an attachment$/ do + @result.should be_an(EC2::Attachment) +end + +Then /^the attachment status should eventually be "([^\"]*)"$/ do |status| + eventually { @attachment.status.should == status.to_sym } +end + +Then /^the attachment status should eventually reflect the detachment$/ do + eventually { [:detaching, :detached, nil].should include(@attachment.status) } +end + +When /^I detach the volume from the instance as device "([^\"]*)"$/ do |device| + @volume.detach_from(@instance, device) +end + +Then /^the volume should eventually be deleted$/ do + eventually do + @volume.should(satisfy do |v| + !v.exists? or + [:deleting, :deleted].include?(v.status) + end) + end +end + +When /^I compute a hash of volume ID to status$/ do + @ec2.volumes.inject({}) do |hash, volume| + hash[volume.id] = volume.status + hash + end +end + +When /^I count the volumes in my account$/ do + @ec2.volumes.inject(0) { |count, volume| count + 1 } +end + +When /^I list the volume attachments$/ do + @result = @volume.attachments +end + +Then /^the result should include the attachment I created$/ do + @result.should include(@attachment) +end diff --git a/features/ec2/step_definitions/zones.rb b/features/ec2/step_definitions/zones.rb new file mode 100644 index 00000000000..57118ace80f --- /dev/null +++ b/features/ec2/step_definitions/zones.rb @@ -0,0 +1,34 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I ask for the list of EC2 availability zones$/ do + @result = @ec2.availability_zones.to_a +end + +Then /^the result should contain the following zone information:$/ do |table| + table.hashes.each do |h| + @result.map { |r| r.name }.should include(h["name"]) + @result.map { |r| r.region.name }.should include(h["region"]) + end +end + +When /^I group the availability zones by state$/ do + @ec2.availability_zones.inject({}) do |hash, zone| + (hash[zone.state] ||= []) << zone + hash + end +end + +When /^I count the availability zones$/ do + @ec2.availability_zones.inject(0) { |count, zone| count + 1 } +end diff --git a/features/ec2/tagging.feature b/features/ec2/tagging.feature new file mode 100644 index 00000000000..9d35b2d6c89 --- /dev/null +++ b/features/ec2/tagging.feature @@ -0,0 +1,106 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +@ec2 @tagging +Feature: EC2 Tagging + + As a user of the high-level interface for EC2 + I want to tag resources + + @instances @slow + Scenario: Tagging an instance + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And the instance status should eventually be "running" + When I call the "instance" resource + Then the resource should be taggable + + @instances @memoized + Scenario: Instance tags memoized from DescribeInstances + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And the instance status should eventually be "running" + When I call the "instance" resource + Then the resource should memoize tags properly + + @security_groups + Scenario: Tagging security group + Given I create a security group + When I call the "security_group" resource + Then the resource should be taggable + + @security_groups @memoized + Scenario: Security group tags memoized from DescribeSecurityGroups + Given I create a security group + When I call the "security_group" resource + Then the resource should memoize tags properly + + @images + Scenario: Tagging an image + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | description | foobar | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And the image description should eventually be "foobar" + When I call the "image" resource + Then the resource should be taggable + + @images @memoized + Scenario: Image tags memoized from DescribeImages + Given I create an image with the following parameters: + | parameter | value | + | name | my-image | + | description | foobar | + | image_location | aws-sdk-amis/quickstart/image.manifest.xml | + And the image description should eventually be "foobar" + When I call the "image" resource + Then the resource should memoize tags properly + + @volumes + Scenario: Tagging a volume + Given I create a volume + When I call the "volume" resource + Then the resource should be taggable + + @volumes @memoized + Scenario: Volume tags memoized from DescribeVolumes + Given I create a volume + When I call the "volume" resource + Then the resource should memoize tags properly + + @snapshots + Scenario: Tagging a snapshot + Given I create a snapshot + When I call the "snapshot" resource + Then the resource should be taggable + + @snapshots @memoized + Scenario: Snapshot tags memoized from DescribeSnapshots + Given I create a snapshot + When I call the "snapshot" resource + Then the resource should memoize tags properly + + @memoized + Scenario: Listing all tags with memoization + Given I create a volume + And I call the "volume" resource + And I tag the resource "ruby-test-1-key" with the value "ruby-test-1-value" + And I start a memoization block + When I compute the set of all tag values + And I list all tagged resources grouped by the tag values of "ruby-test-1-key" + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeTags | diff --git a/features/ec2/volume_attributes.feature b/features/ec2/volume_attributes.feature new file mode 100644 index 00000000000..955a389266d --- /dev/null +++ b/features/ec2/volume_attributes.feature @@ -0,0 +1,43 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @volumes +Feature: Volume attributes + + As a user of the high-level interface for EC2 + I want to read attributes of volumes + So that I can better manage my EBS volumes. + + Scenario: Get volume size + Given I create a volume with the following parameters: + | parameter | value | + | size | 1 | + | availability_zone | us-east-1a | + Then the volume size should be 1 + + Scenario: Get volume snapshot ID + Given I create a snapshot + When I create a volume from the snapshot + Then the volume snapshot should match the snapshot it was created from + + Scenario: Get volume availability zone + Given I create a volume with the following parameters: + | parameter | value | + | size | 1 | + | availability_zone | us-east-1a | + Then the volume availability zone should be "us-east-1a" + + Scenario: Get volume create time + Given I create a volume + Then the volume create time should be within an hour of now diff --git a/features/ec2/volumes.feature b/features/ec2/volumes.feature new file mode 100644 index 00000000000..3ae891e47bb --- /dev/null +++ b/features/ec2/volumes.feature @@ -0,0 +1,191 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @volumes +Feature: Basic Volume Operations + + As a user of the high-level interface for EC2 + I want to create, list, and delete volumes + So that I can use EBS. + + Scenario: Create empty volume + When I create a volume with the following parameters: + | parameter | value | + | size | 1 | + | availability_zone | us-east-1a | + Then the result should be a volume + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | CreateVolume | + | param | Size | 1 | + | param | AvailabilityZone | us-east-1a | + + @memoized + Scenario: Attributes memoized from CreateVolume + Given I start a memoization block + And I create a volume + When I get the following attributes of the volume: + | size | + | snapshot | + | availability_zone | + | status | + | create_time | + Then no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeVolumes | + + Scenario: Create a volume from a snapshot + Given I create a snapshot + When I create a volume from the snapshot + Then the result should be a volume + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | CreateVolume | + | param | AvailabilityZone | us-east-1a | + | param_match | SnapshotId | snap-.+ | + + Scenario: List volumes + Given I create a volume + When I ask for the list of volumes + Then the volume I created should be in the list + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeVolumes | + + @memoized + Scenario: List volumes with memoization + Given I start a memoization block + And I create a volume + When I compute a hash of volume ID to status + And I count the volumes in my account + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeVolumes | + + Scenario: Delete volume + Given I create a volume + When I delete the volume + Then the volume should eventually be deleted + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DeleteVolume | + | param_match | VolumeId | vol-.+ | + + @slow @attachment + Scenario: Attach volume + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I create a 1GiB volume in the same availability zone as the instance + And I wait for the instance status to be "running" + When I attach the volume to the instance as device "/dev/sdf" + Then the result should be an attachment + And the attachment status should eventually be "attached" + And the volume should have an attachment to the instance + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | AttachVolume | + | param_match | VolumeId | vol-.+ | + | param_match | InstanceId | i-.+ | + | param | Device | /dev/sdf | + + @memoized @slow @attachment @wip + Scenario: Attach volume with memoization + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I create a 1GiB volume in the same availability zone as the instance + And I wait for the instance status to be "running" + And I start a memoization block + When I attach the volume to the instance as device "/dev/sdf" + Then the result should be an attachment + And the attachment status should eventually be "attaching" + And the volume should have an attachment to the instance + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | AttachVolume | + | param_match | VolumeId | vol-.+ | + | param_match | InstanceId | i-.+ | + | param | Device | /dev/sdf | + And no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeVolumes | + + @slow @attachment + Scenario: Detach volume + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I create a 1GiB volume in the same availability zone as the instance + And I wait for the instance status to be "running" + And I attach the volume to the instance as device "/dev/sdf" + When I detach the volume from the instance as device "/dev/sdf" + Then the attachment status should eventually reflect the detachment + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DetachVolume | + | param_match | VolumeId | vol-.+ | + | param_match | InstanceId | i-.+ | + | param | Device | /dev/sdf | + + @memoized @slow @attachment @wip + Scenario: Detach volume + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I create a 1GiB volume in the same availability zone as the instance + And I wait for the instance status to be "running" + And I attach the volume to the instance as device "/dev/sdf" + And I start a memoization block + When I detach the volume from the instance as device "/dev/sdf" + Then the attachment status should eventually be "detaching" + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DetachVolume | + | param_match | VolumeId | vol-.+ | + | param_match | InstanceId | i-.+ | + | param | Device | /dev/sdf | + And no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeVolumes | + + @memoized @wip + Scenario: List attachments with memoization + Given I request to run an instance with the following parameters: + | parameter | value | + | image_id | ami-8c1fece5 | + And I create a 1GiB volume in the same availability zone as the instance + And I wait for the instance status to be "running" + And I attach the volume to the instance as device "/dev/sdf" + And I start a memoization block + When I count the volumes in my account + And I list the volume attachments + Then the result should include the attachment I created + And exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeVolumes | + + Scenario: Get volume by ID + When I ask for the volume "vol-123" by ID + Then the result should be a volume + + Scenario: Check that a volume exists (does not exist) + Given I ask for the volume "vol-123" by ID + When I ask if the volume exists + Then the result should be false + + Scenario: Check that an volume exists (exists) + Given I create a volume + When I ask if the volume exists + Then the result should eventually be true diff --git a/features/ec2/zones.feature b/features/ec2/zones.feature new file mode 100644 index 00000000000..70398300274 --- /dev/null +++ b/features/ec2/zones.feature @@ -0,0 +1,35 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ec2 @zones +Feature: Availability Zones + + As a user of the high-level interface for EC2 + I want to find out which availability zones are available for me to use + So I can make my application more tolerant to single-datacenter failures + + Scenario: List availability zones + When I ask for the list of EC2 availability zones + Then the result should contain the following zone information: + | name | region | + | us-east-1a | us-east-1 | + + @memoized + Scenario: List availability zones with memoization + Given I start a memoization block + When I group the availability zones by state + And I count the availability zones + Then exactly 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DescribeAvailabilityZones | diff --git a/features/record/count.feature b/features/record/count.feature new file mode 100644 index 00000000000..f55a2327061 --- /dev/null +++ b/features/record/count.feature @@ -0,0 +1,95 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@orm @count +Feature: Scoped counts + + As an ORM user + I want to use an expressive interface + So that I can count records. + + @query @where + Scenario: Using count with where (hash style) + When I count items with the following expression: + """ + count(:where => { :name => 'Patt' }) + """ + Then a select should have been performed like: + | PART | VALUE | + | condition | `name` = 'Patt' | + | output_list | count(*) | + + @query @where + Scenario: Using count with where (questionmark placeholder style) + When I count items with the following expression: + """ + count(:where => ['name = ?', 'Patt']) + """ + Then a select should have been performed like: + | PART | VALUE | + | condition | name = 'Patt' | + | output_list | count(*) | + + @query @where + Scenario: Using count with where (symbol placeholder style) + When I count items with the following expression: + """ + count(:where => ['name = :name', {:name => 'Patt'}]) + """ + Then a select should have been performed like: + | PART | VALUE | + | condition | name = 'Patt' | + | output_list | count(*) | + + @query @limit + Scenario: Using count with limit + When I count items with the following expression: + """ + count(:limit => 10) + """ + Then a select should have been performed like: + | PART | VALUE | + | limit | 10 | + + @query @where @limit + Scenario: Using count with where and limit + When I count items with the following expression: + """ + count(:where => { :name => 'Patt' }, :limit => 10) + """ + Then a select should have been performed like: + | PART | VALUE | + | output_list | count(*) | + | condition | `name` = 'Patt' | + | limit | 10 | + + Scenario: Adding records and then counting them + Given I configure the example class with: + """ + string_attr :name + """ + And I create a record with the following attributes: + | ATTRIBUTE | VALUE | + | name | my record | + And I create a record with the following attributes: + | ATTRIBUTE | VALUE | + | name | my record | + And I create a record with the following attributes: + | ATTRIBUTE | VALUE | + | name | my item | + When I count items with the following expression: + """ + where('name = ?', "my record").count + """ + Then the result should be 2 diff --git a/features/record/dirty_tracking.feature b/features/record/dirty_tracking.feature new file mode 100644 index 00000000000..0e44ef45765 --- /dev/null +++ b/features/record/dirty_tracking.feature @@ -0,0 +1,69 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@orm @dirty_tracking +Feature: Dirty Tracking + + As an ORM user + I want to be able to introspect changes to my model + + Scenario: New instances should have no changes + Given I configure the example class with: + """ + string_attr :name + """ + When I create a model instance + Then the instance should have no changes + + Scenario: Changing attributes + Given I configure the example class with: + """ + string_attr :name + string_attr :tags, :set => true + """ + When I create a model instance with the following values: + | attribute | value | + | name | my book | + | tags | fiction | + | tags | popular | + Then the following attributes should be changed: + | attribute | + | name | + | tags | + + Scenario: partial updates + Given I configure the example class with: + """ + string_attr :name + integer_attr :age, :precision => 3 + """ + And I create a model instance with the following values: + | attribute | value | + | name | john doe | + | age | 40 | + And I call save on the model instance + And I set the model instance "name" to "new name" + When I call save on the model instance + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Attribute.1.Replace | true | + | param | Attribute.1.Value | new name | + | param | Attribute.1.Name | name | + And no requests should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Attribute.2.Replace | true | + | param | Attribute.2.Value | 040 | + | param | Attribute.2.Name | age | diff --git a/features/record/find.feature b/features/record/find.feature new file mode 100644 index 00000000000..70051b8e9cb --- /dev/null +++ b/features/record/find.feature @@ -0,0 +1,108 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@orm @find +Feature: Scoped Finds + + As an ORM user + I want to use an expressive interface + So that I can find records. + + @query @where + Scenario: Using find with where (hash style) + When I enumerate items with the following expression: + """ + find(:all, :where => { :name => 'Patt' }) + """ + Then a select should have been performed like: + | PART | VALUE | + | condition | `name` = 'Patt' | + + @query @where + Scenario: Using find with where (questionmark placeholder style) + When I enumerate items with the following expression: + """ + find(:all, :where => ['name = ?', 'Patt']) + """ + Then a select should have been performed like: + | PART | VALUE | + | condition | name = 'Patt' | + + @query @where + Scenario: Using find with where (symbol placeholder style) + When I enumerate items with the following expression: + """ + find(:all, :where => ['name = :name', {:name => 'Patt'}]) + """ + Then a select should have been performed like: + | PART | VALUE | + | condition | name = 'Patt' | + + @query @order + Scenario: Using find with order + When I enumerate items with the following expression: + """ + find(:all, :order => [:name, :desc]) + """ + Then a select should have been performed like: + | PART | VALUE | + | sort_instructions | `name` DESC | + + @query @limit + Scenario: Using find with limit + When I enumerate items with the following expression: + """ + find(:all, :limit => 10) + """ + Then a select should have been performed like: + | PART | VALUE | + | limit | 10 | + + @query @limit + Scenario: Using find with limit + When I evaluate the following expression: + """ + find(:first) + """ + Then a select should have been performed like: + | PART | VALUE | + | limit | 1 | + + @query @where @order @limit + Scenario: Using find with select, where, order and limit + When I enumerate items with the following expression: + """ + find(:all, :where => { :name => 'Patt' }, :order => [:name, :desc], :limit => 10) + """ + Then a select should have been performed like: + | PART | VALUE | + | output_list | * | + | condition | `name` = 'Patt' | + | sort_instructions | `name` DESC | + | limit | 10 | + + Scenario: Adding a record and then finding it + Given I configure the example class with: + """ + string_attr :name + """ + And I create a record with the following attributes: + | ATTRIBUTE | VALUE | + | name | my record | + When I enumerate items with the following expression: + """ + where('name = ?', "my record") + """ + Then the records should include the record I created + And its "name" attribute should have the value "my record" diff --git a/features/record/optimistic_locking.feature b/features/record/optimistic_locking.feature new file mode 100644 index 00000000000..e518a7fe2f2 --- /dev/null +++ b/features/record/optimistic_locking.feature @@ -0,0 +1,84 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@orm @optimistic_locking +Feature: Optimistic Locking + + Allows multiple users to access the same record for edits + Assumes a minimum of conflicts with the data. + It does this by checking whether another process has made changes to a + record since it was opened, a SimpleModel::StaleObjectError is thrown + if that has occurred and the update is ignored. + + Scenario: Optimistic locking ads version tracking column + Given I configure the example class with: + """ + string_attr :name + optimistic_locking :version_id + """ + And I create a model instance with the following values: + | attribute | value | + | name | abc | + When I call save on the model instance + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Attribute.2.Name | version_id | + | param | Attribute.2.Value | 1 | + | param | Attribute.2.Replace | false | + | param | Expected.1.Name | version_id | + | param | Expected.1.Exists | false | + + Scenario: Updating Adding optimstic locking after records have already been saved + Given I configure the example class with: + """ + string_attr :name + optimistic_locking :version_id + """ + And I create a model instance with the following values: + | attribute | value | + | name | old name | + And I call save on the model instance + When I set the model instance "name" to "new name" + And I call save on the model instance + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Expected.1.Name | version_id | + | param | Expected.1.Value | 1 | + | param | Attribute.2.Name | version_id | + | param | Attribute.2.Value | 2 | + | param | Attribute.2.Replace | true | + + Scenario: Adding optimstic locking after records have already been saved + Given I configure the example class with: + """ + string_attr :name + """ + And I create a model instance with the following values: + | attribute | value | + | name | abc | + And I call save on the model instance + When I enable optimistic locking for the example class + And I reload the model instance + And I set the model instance "name" to "new name" + And I call save on the model instance + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Expected.1.Name | version_id | + | param | Expected.1.Exists | false | + | param | Attribute.2.Replace | true | + | param | Attribute.2.Value | 1 | + | param | Attribute.2.Name | version_id | diff --git a/features/record/scope.feature b/features/record/scope.feature new file mode 100644 index 00000000000..6b407bf893f --- /dev/null +++ b/features/record/scope.feature @@ -0,0 +1,118 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@orm @scope +Feature: Scoped Finds + + As an ORM user + I want to use an expressive interface + So that I can find records. + + @where + Scenario: Using where with a string + When I enumerate items with the following expression: + """ + where('name = "joe"') + """ + Then a select should have been performed like: + | PART | VALUE | + | condition | name = "joe" | + + @where + Scenario: Using where with a string + When I enumerate items with the following expression: + """ + where(:name => "joe") + """ + Then a select should have been performed like: + | PART | VALUE | + | condition | `name` = 'joe' | + + @where + Scenario: Using where with question mark placeholders + When I enumerate items with the following expression: + """ + where('name = ?', 'joe') + """ + Then a select should have been performed like: + | PART | VALUE | + | condition | name = 'joe' | + + @where + Scenario: Using where with named placeholders + When I enumerate items with the following expression: + """ + where('name = :name', :name => 'joe') + """ + Then a select should have been performed like: + | PART | VALUE | + | condition | name = 'joe' | + + @where @wip + Scenario: Using named integer attributes in where condition hashes should + properly pad the numeric value. + Given I configure the example class with: + """ + integer_attr :price, :precision => 5 + """ + When I enumerate items with the following expression: + """ + where(:price => 123) + """ + Then a select should have been performed like: + | PART | VALUE | + | condition | `price` = '00123' | + + @order + Scenario: Using order with default direction + When I enumerate items with the following expression: + """ + order('name') + """ + Then a select should have been performed like: + | PART | VALUE | + | sort_instructions | `name` ASC | + + @order + Scenario: Using order with a direction + When I enumerate items with the following expression: + """ + order(:name, :desc) + """ + Then a select should have been performed like: + | PART | VALUE | + | sort_instructions | `name` DESC | + + @limit + Scenario: Using limit + When I enumerate items with the following expression: + """ + limit(10) + """ + Then a select should have been performed like: + | PART | VALUE | + | limit | 10 | + + @where @order @limit + Scenario: Using limit + When I enumerate items with the following expression: + """ + where('age > ?', 20).order(:age, :desc).limit(10) + """ + Then a select should have been performed like: + | PART | VALUE | + | output_list | * | + | condition | age > '20' | + | sort_instructions | `age` DESC | + | limit | 10 | diff --git a/features/record/step_definitions/count.rb b/features/record/step_definitions/count.rb new file mode 100644 index 00000000000..05ca1679ec8 --- /dev/null +++ b/features/record/step_definitions/count.rb @@ -0,0 +1,16 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Given /^I count items with the following expression:$/ do |expression| + @result = ExampleClass.module_eval(expression) +end diff --git a/features/record/step_definitions/dirty_tracking.rb b/features/record/step_definitions/dirty_tracking.rb new file mode 100644 index 00000000000..c8a62f803c5 --- /dev/null +++ b/features/record/step_definitions/dirty_tracking.rb @@ -0,0 +1,28 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Then /^the following attributes should be changed:$/ do |table| + table.hashes.collect{|h| h['attribute'] }.sort.should == @inst.changed.sort +end + +Then /^the instance should have no changes$/ do + @inst.changed.should == [] +end + +When /^I set the model instance "([^"]*)" to "([^"]*)"$/ do |method, value| + @inst.send("#{method}=", value) +end + +When /^I call save on the model instance$/ do + @inst.save +end diff --git a/features/record/step_definitions/optimistic_locking.rb b/features/record/step_definitions/optimistic_locking.rb new file mode 100644 index 00000000000..744eff6eb3e --- /dev/null +++ b/features/record/step_definitions/optimistic_locking.rb @@ -0,0 +1,21 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I enable optimistic locking for the example class$/ do + ExampleClass.optimistic_locking :version_id +end + +When /^I reload the model instance$/ do + sleep(1) + @inst = ExampleClass.find(@inst.id) +end diff --git a/features/record/step_definitions/orm.rb b/features/record/step_definitions/orm.rb new file mode 100644 index 00000000000..18d09c09d32 --- /dev/null +++ b/features/record/step_definitions/orm.rb @@ -0,0 +1,65 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Around("@orm") do |scenario,block| + + class ExampleClass < AWS::Record::Base; end + + block.call + + Object.send(:remove_const, :ExampleClass) + +end + +Around("@orm", "~@validations") do |scenario,block| + + SimpleDB.new.domains.create(ExampleClass.domain_name) + + block.call + + SimpleDB.new.domains[ExampleClass.domain_name].delete! + +end + +Given /^I configure the example class with:$/ do |expression| + ExampleClass.module_eval(expression) +end + +Given /^I enumerate items with the following expression:$/ do |expression| + AWS::SimpleDB.consistent_reads do + @records = ExampleClass.module_eval(expression).to_a + end +end + +Given /^I evaluate the following expression:$/ do |expression| + @result = ExampleClass.module_eval(expression) +end + +Given /^I create a record with the following attributes:$/ do |table| + data = {} + table.hashes.each do |hash| + data[hash['ATTRIBUTE']] = hash['VALUE'] + end + @record = ExampleClass.new(data) + @record.save! +end + +Then /^the records should include the record I created$/ do + @found_record = @records.detect{|r| r.id == @record.id } + @found_record.should be_a(ExampleClass) +end + +Then /^its "([^"]*)" attribute should have the value "([^"]*)"$/ do |attr,value| + @found_record.attributes[attr].to_s.should == value +end + diff --git a/features/record/step_definitions/validations.rb b/features/record/step_definitions/validations.rb new file mode 100644 index 00000000000..bfc5fb58213 --- /dev/null +++ b/features/record/step_definitions/validations.rb @@ -0,0 +1,47 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I create a model instance$/ do + @inst = ExampleClass.new +end + +Then /^the instance (should|should not) be valid$/ do |should| + @inst.valid? + if should == "should" + @inst.errors.should == {} + end + @inst.valid?.should send(should == "should" ? :be_true : :be_false) +end + +Then /^the errors should include:$/ do |table| + table.hashes.each do |hash| + @inst.errors.should have_key(hash["attribute"].to_sym) + @inst.errors[hash["attribute"].to_sym].should include(hash["message"]) + end +end + +When /^I create a model instance with the following values:$/ do |table| + opts = {} + table.hashes.each do |hash| + (opts[hash["attribute"].to_sym] ||= []) << hash["value"] + end + + # flatten single values + opts.each_pair do |k,v| + if v.length == 1 + opts[k] = v.first + end + end + + @inst = ExampleClass.new(opts) +end diff --git a/features/record/validations.feature b/features/record/validations.feature new file mode 100644 index 00000000000..4a598383f74 --- /dev/null +++ b/features/record/validations.feature @@ -0,0 +1,208 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@orm @validations +Feature: Validations + + I want to easily validate the values of attributes on my model classes + So that I don't store invalid records + + Scenario: Validate presence of an attribute + Given I configure the example class with: + """ + string_attr :first_name + validates_presence_of :first_name + """ + When I create a model instance + Then the instance should not be valid + And the errors should include: + | attribute | message | + | first_name | may not be blank | + + Scenario: Validate presence of an array-valued attribute (not present) + Given I configure the example class with: + """ + string_attr :first_name, :set => true + validates_presence_of :first_name + """ + When I create a model instance + Then the instance should not be valid + And the errors should include: + | attribute | message | + | first_name | may not be blank | + + Scenario: Validate presence of an array-valued attribute (one value) + Given I configure the example class with: + """ + string_attr :first_name, :set => true + validates_presence_of :first_name + """ + When I create a model instance with the following values: + | attribute | value | + | first_name | fred | + Then the instance should be valid + + Scenario: Validate exclusion (invalid value) + Given I configure the example class with: + """ + string_attr :permission + string_attr :username + validates_exclusion_of( + :permission, + :in => %w(write), + :unless => Proc.new { |user| user.username == "admin" } + ) + """ + When I create a model instance with the following values: + | attribute | value | + | username | fred | + | permission | write | + Then the instance should not be valid + And the errors should include: + | attribute | message | + | permission | is reserved | + + Scenario: Validate inclusion (valid array value) + Given I configure the example class with: + """ + string_attr :permissions, :set => true + string_attr :username + validates_inclusion_of( + :permissions, + :in => %w(read write admin), + :unless => Proc.new { |user| user.username == "admin" } + ) + """ + When I create a model instance with the following values: + | attribute | value | + | username | fred | + | permissions | write | + | permissions | read | + Then the instance should be valid + + Scenario: Validate inclusion (invalid array value) + Given I configure the example class with: + """ + string_attr :permissions, :set => true + string_attr :username + validates_inclusion_of :permissions, + :in => %w(read write admin), + :unless => Proc.new { |user| user.username == "admin" } + """ + When I create a model instance with the following values: + | attribute | value | + | username | fred | + | permissions | write | + | permissions | read | + | permissions | destroy | + Then the instance should not be valid + And the errors should include: + | attribute | message | + | permissions | is not included in the list | + + Scenario: Validate format (valid array value) + Given I configure the example class with: + """ + string_attr :phone_numbers, :set => true + string_attr :username + validates_format_of :phone_numbers, + :with => /(\(\d{3}\) |\d{3}.)?\d{3}[-.]\d{4}/ + """ + When I create a model instance with the following values: + | attribute | value | + | username | fred | + | phone_numbers | (206) 123-4567 | + | phone_numbers | 206.888.1234 | + Then the instance should be valid + + Scenario: Validate format (invalid array value) + Given I configure the example class with: + """ + string_attr :phone_numbers, :set => true + string_attr :username + validates_format_of :phone_numbers, + :with => /(\(\d{3}\) |\d{3}.)?\d{3}[-.]\d{4}/ + """ + When I create a model instance with the following values: + | attribute | value | + | username | fred | + | phone_numbers | (206) 123-456 | + Then the instance should not be valid + And the errors should include: + | attribute | message | + | phone_numbers | is invalid | + + Scenario: Validate count (valid array value) + Given I configure the example class with: + """ + string_attr :phone_numbers, :set => true + string_attr :username + validates_count_of :phone_numbers, :within => 1..2 + """ + When I create a model instance with the following values: + | attribute | value | + | username | fred | + | phone_numbers | (206) 123-4567 | + | phone_numbers | 206.888.1234 | + Then the instance should be valid + + Scenario: Validate count (invalid array value) + Given I configure the example class with: + """ + string_attr :phone_numbers, :set => true + string_attr :username + validates_count_of :phone_numbers, + :within => 1..2, + :too_many => "has too many numbers (maximum is 2)" + """ + When I create a model instance with the following values: + | attribute | value | + | username | fred | + | phone_numbers | (206) 123-4567 | + | phone_numbers | 206.888.1234 | + | phone_numbers | 206.222.3333 | + Then the instance should not be valid + And the errors should include: + | attribute | message | + | phone_numbers | has too many numbers (maximum is 2) | + + Scenario: Validate numericality (valid array value) + Given I configure the example class with: + """ + string_attr :ranks, :set => true + string_attr :username + validates_numericality_of :ranks, :only_integer => true + """ + When I create a model instance with the following values: + | attribute | value | + | username | fred | + | ranks | 10 | + | ranks | 15 | + Then the instance should be valid + + Scenario: Validate numericality (invalid array value) + Given I configure the example class with: + """ + string_attr :ranks, :set => true + string_attr :username + validates_numericality_of :ranks, :only_integer => true + """ + When I create a model instance with the following values: + | attribute | value | + | username | fred | + | ranks | 10.2 | + Then the instance should not be valid + And the errors should include: + | attribute | message | + | ranks | must be an integer | diff --git a/features/s3/async_buckets.feature b/features/s3/async_buckets.feature new file mode 100644 index 00000000000..56a6aef4d1f --- /dev/null +++ b/features/s3/async_buckets.feature @@ -0,0 +1,55 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @low_level @async +Feature: CRUD Buckets (async) + As a user of the S3 low-level client + I want to be able to perform basic CRUD operations on buckets in an asynchronous fashion + + @create_bucket + Scenario: Create a bucket + When I call create_bucket asynchronously + Then the result should be a response handle + And the bucket should exist on completion + + @create_bucket + Scenario: Invalid bucket name + When I ask the client to asynchronously create a bucket with an invalid name + Then the client should raise an argument error + + @create_bucket + Scenario: DNS-incompatible bucket name + When I ask the client to asynchronously create a bucket named "my_bucket" + Then the result should be a response handle + And the bucket should exist on completion + + @delete_bucket + Scenario: Delete bucket + Given I call create_bucket + When I ask the client to asynchronously delete the bucket + Then the result should be a response handle + And the bucket should not exist on completion + + @delete_bucket + Scenario: Delete bucket that does not exist + When I ask the client to asynchronously delete a bucket that does not exist + Then the result should be a response handle + And a client error should be available on failure + + @list_buckets + Scenario: List buckets + Given I call create_bucket + When I ask the client to asynchronously list buckets + Then the result should be a response handle + And the bucket should be in the response on completion diff --git a/features/s3/high_level/bucket_acls.feature b/features/s3/high_level/bucket_acls.feature new file mode 100644 index 00000000000..236c02ba045 --- /dev/null +++ b/features/s3/high_level/bucket_acls.feature @@ -0,0 +1,40 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @high_level @bucket_acl +Feature: Bucket ACLs + As a user of the S3 high-level interface + I want to be able to manipulate bucket ACLs + So that I can control access to my S3 resources + + @set + Scenario: Set bucket ACL with a string + Given I call create_bucket + When I set the bucket ACL to a string + Then the client should have made a "PUT" request to the bucket + And the bucket ACL should resemble the one that was set + + @get + Scenario: Get the bucket ACL + Given I have set a bucket ACL + When I ask for the bucket ACL + Then the result should be an AccessControlList object + And the result should have the same grants as the one I set + + @set @roundtrip + Scenario: Change the bucket ACL + Given I call create_bucket + When I change the ACL to include a new grant + Then the client should have made a "PUT" request to the bucket + And the bucket ACL should include the new grant diff --git a/features/s3/high_level/buckets.feature b/features/s3/high_level/buckets.feature new file mode 100644 index 00000000000..4043512a6ad --- /dev/null +++ b/features/s3/high_level/buckets.feature @@ -0,0 +1,80 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @high_level @buckets +Feature: CRUD Buckets (High Level) + As a user of the S3 high-level interface + I want to be able to perform basic CRUD operations on buckets + + @create_bucket + Scenario: Create a bucket + When I create a new bucket + Then I should receive a bucket + And the client should have made a PUT request to the bucket + + @get_bucket + Scenario: Get a bucket + When I ask for the bucket named "foo" + Then I should receive a bucket named "foo" + And the client should not have been called + + @get_bucket + Scenario: Get a bucket using a symbol + When I ask for the bucket named "foo" using a symbol + Then I should receive a bucket named "foo" + And the client should not have been called + + @delete_bucket + Scenario: Delete bucket + Given I call create_bucket + When I delete the bucket + Then the bucket should not exist + And the client should have made a DELETE request to the bucket + + @delete_bucket + Scenario: Delete versioned bucket with object versions + Given I create a new bucket + And I enable versioning on the bucket + And I write "foo1" to the key "key1" + And I write "foo2" to the key "key1" + And I write "bar1" to the key "key2" + When I force delete the bucket + Then the bucket should not exist + + @list_buckets + Scenario: List all my buckets + Given I call create_bucket + When I ask for the list of buckets as an array + Then the result should contain the bucket + And the client should have made a GET request to the service + + @list_buckets + Scenario: Get bucket owner information + Given I call create_bucket + When I ask for the bucket owner + Then the result should be an object with owner ID and name + And the client should have made a GET request to the service + + @list_buckets + Scenario: Get bucket owner information while listing buckets + Given I call create_bucket + And I ask for the list of buckets as an array + When I ask for the bucket owner of the first item in the array + Then the result should be an object with owner ID and name + And the client should have made a GET request to the service + + @create_bucket + Scenario: Creating a bucket with a location constraint + When I create a bucket with the location constraint "EU" + Then the bucket should have the location constraint of "EU" diff --git a/features/s3/high_level/errors.feature b/features/s3/high_level/errors.feature new file mode 100644 index 00000000000..1232e3e8863 --- /dev/null +++ b/features/s3/high_level/errors.feature @@ -0,0 +1,31 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @errors +Feature: Modeled exceptions + + As a user of the S3 high-level interface + I want to get modeled exceptions for client and server errors + So that I can handle those exceptions without doing string comparisons + + Scenario: Client error + When I ask for the objects from a bucket that does not exist + Then I should get a "NoSuchBucket" client exception as follows: + | field | value | + | code | NoSuchBucket | + | message | The specified bucket does not exist | + And the exception object should include the bucket name + And the exception object should include the following fields: + | request_id | + | host_id | diff --git a/features/s3/high_level/object_acls.feature b/features/s3/high_level/object_acls.feature new file mode 100644 index 00000000000..23d4810dfc2 --- /dev/null +++ b/features/s3/high_level/object_acls.feature @@ -0,0 +1,40 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @high_level @object_acl +Feature: Object ACLs + As a user of the S3 high-level interface + I want to be able to manipulate object ACLs + So that I can control access to my S3 resources + + @set + Scenario: Set object ACL with a string + Given my account has an object in it + When I set the object ACL to a string + Then the client should have made a "PUT" request to the object ACL + And the object ACL should resemble the one that was set + + @get + Scenario: Get the object ACL + Given I have set an object ACL + When I ask for the object ACL + Then the result should be an AccessControlList object + And the result should have the same grants as the one I set + + @set @roundtrip + Scenario: Change the object ACL + Given my account has an object in it + When I change the object ACL to include a new grant + Then the client should have made a "PUT" request to the object ACL + And the object ACL should include the new grant diff --git a/features/s3/high_level/objects.feature b/features/s3/high_level/objects.feature new file mode 100644 index 00000000000..c539b17d078 --- /dev/null +++ b/features/s3/high_level/objects.feature @@ -0,0 +1,164 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @high_level @objects +Feature: CRUD Objects (High Level) + + As a user of the S3 high-level interface + I want to be able to perform basic CRUD operations on objects + + @get_object + Scenario: Get an object + When I ask for the object with key "foo" + Then the result should be an s3 object with key "foo" + And the client should not have been called + + @get_object + Scenario: Get an object (with symbol) + When I ask for the object with key "foo" using a symbol + Then the result should be an s3 object with key "foo" + And the client should not have been called + + @put_object + Scenario: Write an object + Given I ask for the object with key "foo" + When I write the string "HELLO" to it + Then the result should be the object with key "foo" + And the object should eventually have "HELLO" as its body + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | PUT | + | http | uri | /foo | + | http | body | HELLO | + + @put_object @multibyte + Scenario: Write an object with a multibyte string + Given I ask for the object with key "foo" + When I write the UTF-8 string "\xE1\x88\xB4" to the object + Then the object should eventually have the bytes "\xE1\x88\xB4" as its body + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | PUT | + | header | content-length | 3 | + + @put_object @multibyte + Scenario: Write an object with a file containing multibyte characters + Given I ask for the object with key "foo" + When I write a UTF-8 file containing "\xE1\x88\xB4" to the object + Then the object should eventually have the bytes "\xE1\x88\xB4" as its body + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | PUT | + | header | content-length | 3 | + + @put_object + Scenario: Write object metadata + Given I ask for the object with key "foo" + When I write data passing metadata attribute "color" with value "blue" + Then the object should eventually have metadata "color" set to "blue" + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | PUT | + | header | x-amz-meta-color | blue | + + @delete_object + Scenario: Delete an object + Given the bucket has an object with key "foo" + When I delete the object with key "foo" + Then The object with key "foo" should eventually not exist + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | DELETE | + | http | uri | /foo | + + @read_object + Scenario: Read an object + Given I ask for the object with key "foo" + And in the bucket the object with key "foo" has the contents "HELLO" + When I read it + Then the result should be "HELLO" + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | GET | + | http | uri | /foo | + + @head_object + Scenario: Get object metadata + Given I ask for the object with key "foo" + And it has metadata "color" set to "blue" + When I ask for the "color" metadata + Then the result should be "blue" + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | HEAD | + | http | uri | /foo | + + @list_objects + Scenario: List all objects + Given the bucket has an object with key "foo" + When I ask for the list of all the objects as an array + Then the result should include the object with key "foo" + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | GET | + | http | host_match | ruby-integration-test-\d+.s3.amazonaws.com | + + @list_objects @paginate + Scenario: List all objects while paginating + Given I have a bucket with 5 keys + When I ask for 3 keys 2 at a time + Then a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | GET | + | param | max-keys | 2 | + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | GET | + | param | max-keys | 1 | + + @copy @to + Scenario: Copy an object to a different key + Given the object "foo1" has the contents "bar1" + And the object "foo2" has the contents "bar2" + When I copy "foo1" to "foo2" + Then the object "foo2" should have the contents "bar1" + + @copy @from + Scenario: Copy an object from another key + Given the object "foo1" has the contents "bar1" + And the object "foo2" has the contents "bar2" + When I copy "foo2" from "foo1" + Then the object "foo2" should have the contents "bar1" + + @copy + Scenario: Copy an versioned object + Given I create a new bucket + And I enable versioning on the bucket + And I write "1" to the key "key1" + And I write "2" to the key "key1" + And I get the oldest version of "key1" + When I copy the versioned object to "key2" + Then the object "key2" should have the contents "1" + + @copy + Scenario: Copy an object but change its metadata + Given I write "a-string" to the key "foo" with the metadata: + | key | value | + | foo | bar | + When I copy the object "foo" to "foo2" with the metadata: + | key | value | + | abc | xyz | + Then the object "foo2" should read "a-string" with the meatadata: + | key | value | + | abc | xyz | diff --git a/features/s3/high_level/policies.feature b/features/s3/high_level/policies.feature new file mode 100644 index 00000000000..03988813104 --- /dev/null +++ b/features/s3/high_level/policies.feature @@ -0,0 +1,55 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @high_level @bucket_policy +Feature: Bucket Policies + As a user of the S3 high-level interface + I want to be able to manipulate bucket policies + So that I can control access to my S3 resources + + @set + Scenario: Set bucket policy + Given I call create_bucket + When I set a bucket policy + Then the client should have made a "PUT" request to the bucket policy + + @get @roundtrip + Scenario: Change bucket policy + Given I call create_bucket + And the bucket has a policy + When I change the bucket policy using the OO interface + Then the bucket policy should resemble the one I set + + @get + Scenario: Get bucket policy + Given I call create_bucket + And the bucket has a policy + When I ask for the bucket policy + Then the result should be the bucket policy + And the client should have made a "GET" request to the bucket policy + + @get + Scenario: Get bucket policy (no policy) + Given I call create_bucket + When I ask for the bucket policy + Then the result should be nil + And the client should have made a "GET" request to the bucket policy + + @delete + Scenario: Delete bucket policy + Given I call create_bucket + And the bucket has a policy + When I delete the bucket policy + And the client should have made a "DELETE" request to the bucket policy + And the policy should not exist diff --git a/features/s3/high_level/post.feature b/features/s3/high_level/post.feature new file mode 100644 index 00000000000..e1248e6f282 --- /dev/null +++ b/features/s3/high_level/post.feature @@ -0,0 +1,128 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @high_level @objects @post +Feature: Pre-signed HTML Form Fields + + As a user of the S3 high-level interface + I want to be able to get pre-signed form fields + so that I can construct an HTML form to do browser-based upload to S3 + + Scenario: Basic Pre-signed POST + When I generate pre-signed form fields for the object "foo" + And I use a regular HTTP client to POST the data "HELLO" + Then the contents of object "foo" should eventually be "HELLO" + + Scenario: Pre-signed POST using the file name + When I generate pre-signed form fields for an upload to "docs/${filename}" + And I use the form to upload "foo.txt" containing "HELLO" + Then the contents of object "docs/foo.txt" should eventually be "HELLO" + + Scenario: Pre-signed POST with various options + When I generate pre-signed form fields for the object "foo" with the following parameters: + | acl | public_read | + | cache_control | something | + | content_type | text/plain | + | content_disposition | attachment | + | content_encoding | gzip | + | expires_header | tomorrow | + And I use a regular HTTP client to POST the data "HELLO" + And I ask for the public URL of object "foo" + And I use a regular HTTP client to GET the URL + Then the response headers should include: + | content-type | text/plain | + | expires | tomorrow | + | cache-control | something | + | content-disposition | attachment | + | content-encoding | gzip | + + Scenario: Pre-signed POST with success redirect + When I generate pre-signed form fields for the object "foo" with the following parameters: + | success_action_redirect | http://aws.amazon.com | + And I use a regular HTTP client to POST the data "HELLO" + Then the response should be a redirect to "http://aws.amazon.com" + + Scenario: Pre-signed POST with custom success status + When I generate pre-signed form fields for the object "foo" with the following parameters: + | success_action_status | 201 | + And I use a regular HTTP client to POST the data "HELLO" + Then the response status code should be 201 + + Scenario: Pre-signed POST with metadata + When I generate pre-signed form fields for the object "foo" with metadata "color" set to "blue" + And I use a regular HTTP client to POST the data "HELLO" + Then the object should eventually have metadata "color" set to "blue" + + Scenario: Pre-signed POST with additional fields + When I generate pre-signed form fields for the object "foo" ignoring the "somethingelse" field + And I use a regular HTTP client to POST the form with the additional field "somethingelse" set to "values" + Then the contents of object "foo" should eventually be "HELLO" + + Scenario Outline: Pre-signed POST with length constraint + When I generate pre-signed form fields for the object "foo" for content lengths between and bytes + And I use a regular HTTP client to POST the data "", ignoring errors + Then the response status code should be + + Examples: + | min | max | data | status | + | 10 | 15 | HI! | 400 | + | 5 | 20 | HELLO | 204 | + | 2 | 5 | HELLO | 204 | + | 2 | 5 | HELLO! | 400 | + + Scenario Outline: Pre-signed POST with key prefix constraint + When I generate pre-signed form fields for keys starting with "" + And I use a regular HTTP client to POST data to "" + Then the response status code should be + + Examples: + | prefix | key | status | + | foo | foobar | 204 | + | foo | foo | 204 | + | foo | fo | 403 | + | foo${filename} | foo${filename} | 403 | + + Scenario Outline: Pre-signed POST with metadata starts-with constraint + When I generate pre-signed form fields where metadata "" starts with "" + And I use a regular HTTP client to POST data with metadata "" set to "" + Then the response status code should be + + Examples: + | constrained meta | prefix | input meta | value | status | + | zipcode | 98 | zip | 98 | 403 | + | zipcode | 98 | zipcode | 98000 | 204 | + | zipcode | 98 | zipcode | 97000 | 403 | + + Scenario Outline: Pre-signed POST with header starts-with constraint + When I generate pre-signed form fields where header "
" starts with "" + And I use a regular HTTP client to POST data with header "
" set to "" + Then the response status code should be + + Examples: + | header option | header | prefix | value | status | + | content_type | Content-Type | image | image/jpeg | 204 | + | content_type | Content-Type | image | text/plain | 403 | + | expires_header | Expires | Thu | Thu, 26 May | 204 | + | expires_header | Expires | Fri | Thu, 26 May | 403 | + + Scenario Outline: Pre-signed POST with expiration + When I generate pre-signed form fields that expire in seconds + And I wait seconds + And I use a regular HTTP client to POST the data "HELLO", ignoring errors + Then the response status code should be + + Examples: + | expiration | sleep | status | + | 1 | 2 | 403 | + | 2 | 1 | 204 | diff --git a/features/s3/high_level/step_definitions.rb b/features/s3/high_level/step_definitions.rb new file mode 100644 index 00000000000..4093417db1c --- /dev/null +++ b/features/s3/high_level/step_definitions.rb @@ -0,0 +1,39 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Given /^I create a new bucket$/ do + create_bucket_high_level +end + +When /^I create a bucket with the location constraint "([^"]*)"$/ do |constraint| + create_bucket_high_level(:location_constraint => constraint) +end + +Given /^I write "([^"]*)" to the key "([^"]*)"$/ do |data, key| + @object = @bucket.objects[key] + @result = @object.write(data) +end + +Given /^I have a bucket with the following keys:$/ do |table| + Given "I create a new bucket" + table.hashes.each do |hash| + Given "I write \"foo\" to the key \"#{hash['key']}\"" + end +end + +Given /^I have a bucket with (\d+) keys$/ do |count| + Given "I create a new bucket" unless @bucket + count.to_i.times do |n| + Given "I write \"foo\" to the key \"#{'%04d' % n}\"" + end +end diff --git a/features/s3/high_level/step_definitions/bucket_acls.rb b/features/s3/high_level/step_definitions/bucket_acls.rb new file mode 100644 index 00000000000..1dcc26a7567 --- /dev/null +++ b/features/s3/high_level/step_definitions/bucket_acls.rb @@ -0,0 +1,67 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I set the bucket ACL to a string$/ do + @s3.buckets[@bucket_name].acl = < + + #{@test_config["owner_id"]} + #{@test_config["display_name"]} + + + + + d25639fbe9c19cd30a4c0f43fbf00e2d3f96400a9aa8dabfbbebe19069d1a5df + Mickey Mouse + + FULL_CONTROL + + + +END +end + +Given /^I have set a bucket ACL$/ do + Given "I call create_bucket" + @bucket = @s3.buckets[@bucket_name] + @bucket.acl = { + :owner => { :id => @bucket.owner.id }, + :grants => [{ :permission => :full_control, + :grantee => { + :canonical_user_id => + "154b2f3550127d0439dfe1e89a03a7a4178048cc05c6fdaeb40796841a5cfcef" + } }] + } +end + +When /^I ask for the bucket ACL$/ do + @result = @bucket.acl +end + +Then /^the result should be an AccessControlList object$/ do + @result.should be_an(S3::AccessControlList) +end + +Then /^the result should have the same grants as the one I set$/ do + @result.grants.map { |g| g.grantee.canonical_user_id }. + should include("154b2f3550127d0439dfe1e89a03a7a4178048cc05c6fdaeb40796841a5cfcef") +end + +When /^I change the ACL to include a new grant$/ do + @s3.buckets[@bucket_name].acl.change do |acl| + acl.grant(:read_acp). + to(:canonical_user_id => + "154b2f3550127d0439dfe1e89a03a7a4178048cc05c6fdaeb40796841a5cfcef") + end +end diff --git a/features/s3/high_level/step_definitions/buckets.rb b/features/s3/high_level/step_definitions/buckets.rb new file mode 100644 index 00000000000..b032188c07a --- /dev/null +++ b/features/s3/high_level/step_definitions/buckets.rb @@ -0,0 +1,66 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Then /^I should receive a bucket$/ do + Then "I should receive a bucket named \"#{@bucket_name}\"" +end + +Then /^I should receive a bucket named "([^\"]*)"$/ do |name| + @bucket.should be_an S3::Bucket + @bucket.name.should == name +end + +When /^I ask for the bucket named "([^\"]*)"$/ do |bucket_name| + @bucket = @s3.buckets[bucket_name] +end + +When /^I ask for the bucket named "([^\"]*)" using a symbol$/ do |bucket_name| + @bucket = @s3.buckets[bucket_name.to_sym] +end + +When /^I ask for the bucket named "([^\"]*)" using a method$/ do |bucket_name| + @bucket = @s3.buckets.send(bucket_name) +end + +When /^I delete the bucket$/ do + @s3.buckets[@bucket_name].delete +end + +When /^I force delete the bucket$/ do + @s3.buckets[@bucket_name].delete! +end + +When /^I ask for the list of buckets as an array$/ do + @buckets = @s3.buckets.to_a +end + +Then /^the result should contain the bucket$/ do + @buckets.should include(S3::Bucket.new(@bucket_name)) +end + +When /^I ask for the bucket owner$/ do + @owner = @s3.buckets[@bucket_name].owner +end + +When /^I ask for the bucket owner of the first item in the array$/ do + @owner = @buckets.first.owner +end + +Then /^the result should be an object with owner ID and name$/ do + @owner.id.should == @test_config["owner_id"] + @owner.display_name.should == @test_config["display_name"] +end + +Then /^the bucket should have the location constraint of "([^"]*)"$/ do |constraint| + @bucket.location_constraint.should == constraint +end diff --git a/features/s3/high_level/step_definitions/errors.rb b/features/s3/high_level/step_definitions/errors.rb new file mode 100644 index 00000000000..99af1d6d03b --- /dev/null +++ b/features/s3/high_level/step_definitions/errors.rb @@ -0,0 +1,44 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I ask for the objects from a bucket that does not exist$/ do + begin + @bucket_name = "!!$$$-#{Time.now.to_i}" + @s3.buckets[@bucket_name].objects.to_a + rescue => e + @exception = e + else + fail + end +end + +Then /^I should get an? "([^\"]*)" (client|server) exception as follows:$/ do |type, cs, table| + @exception.should be_a(cs == "client" ? Errors::ClientError : Errors::ServerError) + klass = S3::Errors.const_get(type) + @exception.should be_a(klass) + table.hashes.each do |hash| + @exception.send(hash["field"]).should == hash["value"] + end +end + +Then /^the exception object should include the bucket name$/ do + @exception.bucket_name.should == @bucket_name +end + +Then /^the exception object should include the following fields:$/ do |table| + table.raw.each do |field| + field = [field].flatten.first + @exception.should respond_to(field) + @exception.send(field).should_not be_blank + end +end diff --git a/features/s3/high_level/step_definitions/object_acls.rb b/features/s3/high_level/step_definitions/object_acls.rb new file mode 100644 index 00000000000..bf0c7622718 --- /dev/null +++ b/features/s3/high_level/step_definitions/object_acls.rb @@ -0,0 +1,50 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I set the object ACL to a string$/ do + @s3.buckets[@bucket_name].objects[@object_key].acl = < + + #{@test_config["owner_id"]} + #{@test_config["display_name"]} + + + + + 154b2f3550127d0439dfe1e89a03a7a4178048cc05c6fdaeb40796841a5cfcef + Mickey Mouse + + FULL_CONTROL + + + +END +end + +Given /^I have set an object ACL$/ do + Given "my account has an object in it" + Given "I set the object ACL to a string" +end + +When /^I ask for the object ACL$/ do + @result = @s3.buckets[@bucket_name].objects[@object_key].acl +end + +When /^I change the object ACL to include a new grant$/ do + @s3.buckets[@bucket_name].objects[@object_key].acl.change do |acl| + acl.grant(:read_acp). + to(:canonical_user_id => + "154b2f3550127d0439dfe1e89a03a7a4178048cc05c6fdaeb40796841a5cfcef") + end +end diff --git a/features/s3/high_level/step_definitions/objects.rb b/features/s3/high_level/step_definitions/objects.rb new file mode 100644 index 00000000000..551afd85525 --- /dev/null +++ b/features/s3/high_level/step_definitions/objects.rb @@ -0,0 +1,220 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Before("@s3", "@objects") do + create_bucket_high_level + @http_handler.requests_made.clear +end + +When /^I ask for the object with key "([^\"]*)"$/ do |key| + @object = @result = @bucket.objects[key] +end + +Then /^the result should be an s3 object with key "([^\"]*)"$/ do |key| + @result.should be_a S3::S3Object + @result.key.should == key +end + +When /^I ask for the object with key "([^\"]*)" using a symbol$/ do |key| + @object = @result = @bucket.objects[key.to_sym] +end + +When /^I ask for the object with key "([^\"]*)" using a method call$/ do |key| + @object = @result = @bucket.objects.send(key) +end + +When /^I write the string "([^\"]*)" to it( with public read access)?$/ do |string, access| + @result = + if access.to_s.empty? + @result.write(string) + else + @result.write(string, :acl => :public_read) + end +end + +Then /^the object should eventually have "([^\"]*)" as its body$/ do |body| + @result.read.should == body +end + +When /^I write data passing metadata attribute "([^\"]*)" with value "([^\"]*)"$/ do |meta_name, meta_value| + @result.write("HELLO", :metadata => { meta_name => meta_value }) +end + +Then /^the object should eventually have metadata "([^\"]*)" set to "([^\"]*)"$/ do |meta, value| + @object.metadata[meta].should == value +end + +When /^I write (\d+) (\d+)\-byte chunks of data using block form$/ do |n, size| + n = n.to_i + size = size.to_i + @whole_size = n*size + @whole_data = (1..@whole_size).map { |i| (i % 10).to_s }.join + chunks = (0...n).map do |i| + @whole_data[i*size, size] + end + @result.write do |stream| + chunks.each { |chunk| stream.write(chunk) } + end +end + +Then /^the object should eventually have the full (\d+) bytes as its body$/ do |arg1| + @result.read.should == @whole_data +end + +Then /^the client should have made a "([^\"]*)" request with the full (\d+)\-byte body$/ do |verb, body| + pending # express the regexp above with the code you wish you had +end + +Given /^the bucket has an object with key "([^\"]*)"$/ do |key| + @bucket.objects[key].write("HELLO") +end + +When /^I delete the object with key "([^\"]*)"$/ do |key| + @bucket.objects[key].delete +end + +Then /^The object with key "([^\"]*)" should eventually not exist$/ do |key| + @bucket.objects.to_a.should_not include(@result) +end + +Given /^in the bucket the object with key "([^\"]*)" has the contents "([^\"]*)"$/ do |key, data| + @bucket.objects[key].write(data) +end + +When /^I read it$/ do + @result = @result.read +end + +Then /^the result should be "([^\"]*)"$/ do |body| + @result.should == body +end + +When /^I ask for the list of all the objects as an array$/ do + @result = @bucket.objects.to_a +end + +When /^I ask for (\d+) keys (\d+) at a time$/ do |limit,batch_size| + options = { :limit => limit.to_i, :batch_size => batch_size.to_i } + @objects = [] + @bucket.objects.each(options) do |obj| + @objects << obj + end +end + + +When /^I ask for all objects (\d+) at a time$/ do |batch_size| + @objects = [] + @bucket.objects.each(:batch_size => batch_size.to_i) do |obj| + @objects << obj + end +end +Then /^the result should include the object with key "([^\"]*)"$/ do |key| + @result.should have(1).item + @result.first.should be_an S3::S3Object + @result.first.key.should == key +end + +Given /^it has metadata "([^\"]*)" set to "([^\"]*)"$/ do |meta, value| + metadata = {} + metadata[meta] = value + @result.write("HELLO", :metadata => metadata) +end + +When /^I ask for the "([^\"]*)" metadata$/ do |meta| + @result = @result.metadata[meta] +end + +Then /^I should have made at least (\d+) "([^\"]*)" bucket requests$/ do |count,verb| + @http_handler.requests_made.select{|req| + req.http_method == verb + }.length.should == count.to_i +end + +Given /^the object "([^"]*)" has the contents "([^"]*)"$/ do |key, data| + @bucket.objects[key].write(data) +end + +When /^I copy "([^"]*)" to "([^"]*)"$/ do |from_key, to_key| + @bucket.objects[from_key].copy_to(@bucket.objects[to_key]) +end + +When /^I copy "([^"]*)" from "([^"]*)"$/ do |to_key, from_key| + @bucket.objects[to_key].copy_from(@bucket.objects[from_key]) +end + +Then /^the object "([^"]*)" should have the contents "([^"]*)"$/ do |key, data| + @bucket.objects[key].read.should == data +end + +Given /^I get the oldest version of "([^"]*)"$/ do |key| + @version = @bucket.objects[key].versions.to_a.last +end + +When /^I copy the versioned object to "([^"]*)"$/ do |key| + @bucket.objects[key].copy_from(@version) +end + +def meta_hash table + table.hashes.inject({}) {|d,h| d[h['key']] = h['value']; d } +end + +Given /^I write "([^"]*)" to the key "([^"]*)" with the metadata:$/ do |data, key, table| + + @bucket.objects[key].write(data, :metadata => meta_hash(table)) +end + +When /^I copy the object "([^"]*)" to "([^"]*)" with the metadata:$/ do |src, dest, table| + @bucket.objects[src].copy_to(dest, :metadata => meta_hash(table)) +end + +Then /^the object "([^"]*)" should read "([^"]*)" with the meatadata:$/ do |key, data, table| + @bucket.objects[key].read.should == data + @bucket.objects[key].metadata.to_h.should == meta_hash(table) +end + +Then /^the contents of object "([^\"]*)" should eventually be "([^\"]*)"$/ do |key, data| + eventually(30) { @bucket.objects[key].read.should == data } +end + +Given /^the object "([^\"]*)" has the contents "([^\"]*)" and a :(\w+) acl$/ do |key, data, acl| + @bucket.objects[key].write(data, :acl => acl.to_sym) +end + +Then /^the result should be the object with key "([^\"]*)"$/ do |key| + @result.should == @bucket.objects[key] +end + +When /^I write the UTF\-8 string "([^\"]*)" to the object$/ do |str| + str = eval("\"#{str}\"") + @object.write(str) +end + +Then /^the object should eventually have the bytes "([^\"]*)" as its body$/ do |str| + str = eval("\"#{str}\"") + str.force_encoding("BINARY") if str.respond_to?(:force_encoding) + @object.read.bytes.to_a.should == str.bytes.to_a +end + +When /^I write a UTF\-8 file containing "([^\"]*)" to the object$/ do |str| + require 'tempfile' + str = eval("\"#{str}\"") + f = Tempfile.new("foo") + begin + f.write(str) + f.flush + @object.write(:file => f.path) + ensure + f.close + f.unlink + end +end diff --git a/features/s3/high_level/step_definitions/policies.rb b/features/s3/high_level/step_definitions/policies.rb new file mode 100644 index 00000000000..1b807e1f909 --- /dev/null +++ b/features/s3/high_level/step_definitions/policies.rb @@ -0,0 +1,57 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I set a bucket policy$/ do + @s3.buckets[@bucket_name].policy = valid_policy +end + +Then /^the client should have made a "([^\"]*)" request to the bucket policy$/ do |method| + r = @http_handler.requests_made.last + r.http_method.should == method + r.host.should == "#@bucket_name.s3.amazonaws.com" + r.uri.should == "/?policy" +end + +When /^I change the bucket policy using the OO interface$/ do + @s3.buckets[@bucket_name].policy.change do |policy| + policy.allow(:actions => "s3:*", + :resources => @bucket_name, + :principals => "681294939609"). + where("aws:SourceIp").is_ip_address("192.168.1.0/24", + "192.168.1.1/32"). + where("aws:SourceIp").not_ip_address("192.168.1.2/32"). + where("s3:prefix").like("photos/*"). + where("aws:SecureTransport").is(true) + end +end + +When /^I ask for the bucket policy$/ do + @result = @s3.buckets[@bucket_name].policy +end + +Then /^the result should be the bucket policy$/ do + @result.statements.size.should == 1 + stmt = @result.statements.first + stmt.sid.should == "Stmt1296685809629" + stmt.effect.should == "Allow" + stmt.resources.join.should include(@bucket_name) + stmt.principals.join.should include("681294939609") +end + +Then /^the result should be nil$/ do + @result.should be_nil +end + +When /^I delete the bucket policy$/ do + @s3.buckets[@bucket_name].policy.delete +end diff --git a/features/s3/high_level/step_definitions/post.rb b/features/s3/high_level/step_definitions/post.rb new file mode 100644 index 00000000000..3f941c43046 --- /dev/null +++ b/features/s3/high_level/step_definitions/post.rb @@ -0,0 +1,142 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I generate pre\-signed form fields for the object "([^\"]*)"$/ do |key| + @form = @result = @bucket.objects[key].presigned_post +end + +When /^I use a regular HTTP client to POST the data "([^\"]*)"$/ do |data| + execute_post(@form, + :data => data) +end + +When /^I generate pre\-signed form fields for an upload to "([^\"]*)"$/ do |key| + @form = @result = @bucket.presigned_post(:key => key) +end + +When /^I use the form to upload "([^\"]*)" containing "([^\"]*)"$/ do |filename, data| + execute_post(@form, :data => data, :filename => filename) +end + +When /^I generate pre\-signed form fields for the object "([^\"]*)" with the following parameters:$/ do |key, table| + opts = table.rows_hash.inject({}) do |opts, (name, value)| + opts[name.to_sym] = case name + when "acl" then value.to_sym + else + value + end + opts + end + @form = @result = @bucket.presigned_post(opts.merge(:key => key)) +end + +Then /^the response should be a redirect to "([^\"]*)"$/ do |url| + @result.should be_a(Net::HTTPRedirection) + @result["location"].should include(url) +end + +Then /^the response status code should be (\d+)$/ do |code| + fail("got wrong status code #{@result.code}: #{@result.body}") unless + @result.code == code +end + +When /^I generate pre\-signed form fields for the object "([^\"]*)" with metadata "([^\"]*)" set to "([^\"]*)"$/ do |key, meta, value| + @object = @bucket.objects[key] + @form = @result = + @object.presigned_post(:metadata => Hash[[[meta, value]]]) +end + +When /^I use a regular HTTP client to POST the form with the additional field "([^\"]*)" set to "([^\"]*)"$/ do |field, value| + execute_post(@form, :additional => Hash[[[field, value]]]) +end + +When /^I generate pre\-signed form fields for the object "([^\"]*)" for content lengths between (\d+) and (\d+) bytes$/ do |key, min, max| + range = (min.to_i)..(max.to_i) + @form = @result = + @bucket.objects[key].presigned_post(:content_length => range) +end + +When /^I generate pre\-signed form fields for keys starting with "([^\"]*)"$/ do |prefix| + @form = @result = + @bucket.presigned_post.where(:key).starts_with(prefix) +end + +When /^I use a regular HTTP client to POST data to "([^\"]*)"$/ do |key| + execute_post(@form, :additional => { "key" => key }, + :ignore_errors => true) +end + +When /^I generate pre\-signed form fields where metadata "([^\"]*)" starts with "([^\"]*)"$/ do |meta, prefix| + @form = @result = + @bucket.presigned_post.where_metadata(meta).starts_with(prefix) +end + +When /^I use a regular HTTP client to POST data with metadata "([^\"]*)" set to "([^\"]*)"$/ do |meta, value| + execute_post(@form, + :additional => Hash[[["x-amz-meta-#{meta}", value]]], + :ignore_errors => true) +end + +When /^I generate pre\-signed form fields for the object "([^\"]*)" ignoring the "([^\"]*)" field$/ do |key, ignored| + @form = @result = + @bucket.presigned_post(:ignore => ignored) +end + +When /^I generate pre\-signed form fields that expire in (\d+) seconds$/ do |expires| + @form = @result = + @bucket.presigned_post(:expires => expires.to_i) +end + +When /^I wait (\d+) seconds$/ do |seconds| + sleep seconds.to_i +end + +When /^I generate pre\-signed form fields where header "([^\"]*)" starts with "([^\"]*)"$/ do |header, prefix| + @form = @result = + @bucket.presigned_post.where(header.to_sym).starts_with(prefix) +end + +When /^I use a regular HTTP client to POST data with header "([^\"]*)" set to "([^\"]*)"$/ do |header, value| + execute_post(@form, :additional => Hash[[[header, value]]], + :ignore_errors => true) +end + +def execute_post(form, opts = {}) + require 'net/http/post/multipart' + + data = opts[:data] || "HELLO" + filename = opts[:filename] || "foo.txt" + + Net::HTTP.start(form.url.host) do |http| + file = UploadIO.new(StringIO.new(data), "text/plain", filename) + + fields = form.fields. + merge(opts[:additional] || {}) + fields["key"] ||= "foo" + + fields = fields.to_a + [["file", file]] + + req = Net::HTTP::Post::Multipart.new(form.url.path, fields) + resp = http.request(req) + @result = resp + + unless + resp.kind_of?(Net::HTTPSuccess) or + resp.kind_of?(Net::HTTPRedirection) or + resp.kind_of?(Net::HTTPForbidden) + then + raise "status code #{resp.code}: #{resp.body}" + end + + end +end diff --git a/features/s3/high_level/step_definitions/tree.rb b/features/s3/high_level/step_definitions/tree.rb new file mode 100644 index 00000000000..a46ce2cefb7 --- /dev/null +++ b/features/s3/high_level/step_definitions/tree.rb @@ -0,0 +1,61 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Given /^I use "([^"]*)" as the delimiter$/ do |delimiter| + @delimiter = delimiter +end + +Given /^I use "([^"]*)" as the prefix$/ do |prefix| + @prefix = prefix +end + +Given /^I choose to not append the delimiter$/ do + @append = false +end + +When /^I access the bucket as a tree$/ do + + options = {} + options[:prefix] = @prefix unless @prefix.nil? + options[:delimiter] = @delimiter unless @delimiter.nil? + options[:append] = @append unless @append.nil? + + @leaves = [] + @branches = [] + @bucket.as_tree(options).children.each do |node| + if node.leaf? + @leaves << node + else + @branches << node + end + end + +end + +Then /^I should receive leaf nodes with the following keys:$/ do |table| + keys = @leaves.collect{|leaf| leaf.key } + keys.should == table.hashes.collect{|hash| hash['key'] } +end + +Then /^I should receive branch nodes with the following prefixes:$/ do |table| + prefixes = @branches.collect{|branch| branch.prefix } + prefixes.should == table.hashes.collect{|hash| hash['prefix'] } +end + +Then /^the branch prefixed "([^"]*)" should have leaves with keys:$/ do |prefix, table| + keys = [] + @branches.find{|branch| branch.prefix == prefix }.children.each do |node| + keys << node.key if node.leaf? + end + keys.should == table.hashes.collect{|hash| hash['key'] } +end diff --git a/features/s3/high_level/step_definitions/uploads.rb b/features/s3/high_level/step_definitions/uploads.rb new file mode 100644 index 00000000000..ab45e4ca0bd --- /dev/null +++ b/features/s3/high_level/step_definitions/uploads.rb @@ -0,0 +1,143 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'tempfile' +require 'digest/md5' + +Given /^the multipart upload threshold is (\d+)(mb|kb)$/ do |size, unit| + bytes = size.to_i * 1024 + bytes *= 1024 if unit == "mb" + AWS.config(:s3_multipart_threshold => bytes) + @bucket = S3::Bucket.new(@bucket.name) +end + +Given /^I have a (\d+)(mb|kb) file$/ do |size, unit| + bytes = size.to_i * 1024 + bytes *= 1024 if unit == "mb" + data = "!"*bytes + @file = Tempfile.new("testfile") + @file.write(data) + @file.close +end + +When /^I write the file to the object "([^\"]*)"$/ do |key| + @bucket.objects[key].write(:data => @file.open) +end + +Then /^the contents of object "([^\"]*)" should eventually match the file$/ do |key| + sleep 1 + object = @bucket.objects[key] + object.head.content_length.should == @file.size + Digest::MD5.hexdigest(object.read).should == + Digest::MD5.file(@file.path).hexdigest +end + +When /^I write the file to the object "([^\"]*)" with the following metadata:$/ do |key, table| + @bucket.objects[key].write(:data => @file.open, + :metadata => table.rows_hash) +end + +Then /^the metadata of object "([^\"]*)" should eventually be:$/ do |key, table| + object = @bucket.objects[key] + sleep 1 until object.head.content_length > 0 + object.head.meta.should == table.rows_hash +end + +When /^I start an upload to object "([^\"]*)"$/ do |key| + @upload = @result = @bucket.objects[key].multipart_upload +end + +Then /^the result should be an upload object$/ do + @result.should be_an(S3::MultipartUpload) +end + +Then /^the list of uploads for the bucket should include the upload$/ do + @bucket.multipart_uploads.to_a.should include(@upload) +end + +When /^I ask for the list of uploads for the bucket$/ do + @result = @bucket.multipart_uploads +end + +Then /^the result should eventually include the upload I started$/ do + eventually(30) { @result.should include(@upload) } +end + +When /^I ask for the upload to "([^\"]*)" with ID "([^\"]*)"$/ do |key, id| + @upload = @result = @bucket.objects[key].multipart_uploads[id] +end + +When /^I ask if the upload exists$/ do + @result = @upload.exists? +end + +When /^I ask for the list of uploads to the object "([^\"]*)"$/ do |key| + @result = @bucket.objects[key].multipart_uploads +end + +When /^I abort the upload$/ do + @upload.abort +end + +Then /^the list of uploads for the bucket should eventually not include the upload I started$/ do + eventually(30) { @bucket.multipart_uploads.should_not include(@upload) } +end + +When /^I upload the following parts:$/ do |table| + table.raw.flatten.each do |data| + @upload.add_part(data) + end +end + +Then /^the parts list should include:$/ do |table| + table.hashes.each do |h| + @upload.parts.map { |p| p.part_number }. + should include(h["part number"].to_i) + @upload.parts[h["part number"].to_i]. + size.should == h["size"].to_i + end +end + +Then /^the last modified date for each part should be within a minute of now$/ do + @upload.parts.all? do |part| + part.last_modified.should be_within(60).of(DateTime.now) + end +end + +When /^I ask for the parts list for the upload$/ do + # sugar +end + +When /^I add a part to the upload with data "([^\"]*)"$/ do |data| + @part = @result = @upload.add_part(data) +end + +Then /^the result should be an uploaded part$/ do + @result.should be_an(S3::UploadedPart) +end + +Then /^the parts list should include the part I uploaded$/ do + @upload.parts.should include(@part) +end + +When /^I complete the upload with the parts uploaded using the object$/ do + @upload.complete +end + +When /^I complete the upload with all parts in S3$/ do + @upload.complete(:remote_parts) +end + +Given /^I get the upload again by ID$/ do + @upload = @upload.object.multipart_uploads[@upload.id] +end diff --git a/features/s3/high_level/step_definitions/urls.rb b/features/s3/high_level/step_definitions/urls.rb new file mode 100644 index 00000000000..baa95a48796 --- /dev/null +++ b/features/s3/high_level/step_definitions/urls.rb @@ -0,0 +1,58 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I generate a pre\-signed (\w+) URL for the object "([^\"]*)"$/ do |method, key| + @url = @result = @bucket.objects[key].url_for(method, :secure => false) +end + +When /^I use a regular HTTP client to GET the URL$/ do + require 'net/http' + @result = Net::HTTP.get_response(@url) +end + +Then /^the response body should be "([^\"]*)"$/ do |body| + @result.body.should == body +end + +When /^I use a regular HTTP client to PUT "([^\"]*)" to the URL$/ do |body| + Net::HTTP.start(@url.host) do |http| + resp = http.send_request("PUT", @url.request_uri, body, { "content-type" => "" }) + resp.body.to_s.should == "" + resp.should be_a(Net::HTTPSuccess) + end +end + +When /^I use a regular HTTP client to DELETE the URL$/ do + Net::HTTP.start(@url.host) do |http| + http.delete(@url.request_uri) + end +end + +When /^I generate a pre\-signed GET URL for the object "([^\"]*)" with the following parameters:$/ do |key, table| + opts = table.rows_hash.inject({}) do |opts, (name, value)| + opts[name.to_sym] = value + opts + end.merge(:secure => false) + @url = @result = @bucket.objects[key].url_for(:get, opts) +end + +Then /^the response headers should include:$/ do |table| + raise @result.body unless @result.kind_of?(Net::HTTPSuccess) + table.rows_hash.each do |name, value| + @result[name].should == value + end +end + +When /^I ask for the public URL of object "([^\"]*)"$/ do |key| + @result = @url = @bucket.objects[key].public_url(:secure => false) +end diff --git a/features/s3/high_level/step_definitions/versions.rb b/features/s3/high_level/step_definitions/versions.rb new file mode 100644 index 00000000000..47819a30fd6 --- /dev/null +++ b/features/s3/high_level/step_definitions/versions.rb @@ -0,0 +1,74 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +include AWS + +When /^I enable versioning on the bucket$/ do + @bucket.enable_versioning + sleep(0.5) +end + +When /^I suspend versioning on the bucket$/ do + @bucket.suspend_versioning + sleep(0.5) +end + +When /^the object should have a version_id$/ do + @object.version_id.should be_a(String) +end + +When /^the bucket should be versioned$/ do + @bucket.versioned?.should == true +end + +When /^the bucket should not be versioned$/ do + @bucket.versioned?.should == false +end + +When /^the bucket versioning state should be "([^"]*)"$/ do |state| + @bucket.versioning_state.should == state.to_sym +end + +Then /^there should be (\d+) versions for the object$/ do |count| + @object.versions.should == count.to_i +end + +Then /^the bucket should have (\d+) versions$/ do |count| + @versions = @bucket.versions.to_a + @versions.length.should == count.to_i +end + +Then /^version (\d+) should contain "([^"]*)"$/ do |n, data| + @versions[n.to_i].read.should == data +end + +Then /^version (\d+) should be a delete marker$/ do |n| + @versions[n.to_i].delete_marker?.should == true +end + +Then /^the latest bucket version should contain "([^"]*)"$/ do |data| + @bucket.versions.latest.read.should == data +end + +When /^I delete the latest bucket version$/ do + @bucket.versions.latest.delete +end + +Then /^the object "([^"]*)" should have (\d+) versions$/ do |key, count| + @versions = @bucket.objects[key].versions.to_a + @versions.length.should == count.to_i +end + +Then /^the result should be the latest version for the key "([^\"]*)"$/ do |key| + @result.should == @bucket.objects[key].versions.latest +end diff --git a/features/s3/high_level/tree.feature b/features/s3/high_level/tree.feature new file mode 100644 index 00000000000..c9ea742f55b --- /dev/null +++ b/features/s3/high_level/tree.feature @@ -0,0 +1,121 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @high_level @tree +Feature: Bucket as tree + + As an S3 user + I want to be able to explore the contents of a bucket as a tree + + Scenario: Using as_tree with defaults + Given I have a bucket with the following keys: + | key | + | videos/wedding.mkv | + | videos/vacation.mkv | + | photos/2009/family.jpg | + | photos/2009/friends.jpg | + | photos/2010/family.jpg | + | README | + When I access the bucket as a tree + Then I should receive leaf nodes with the following keys: + | key | + | README | + And I should receive branch nodes with the following prefixes: + | prefix | + | photos/ | + | videos/ | + + @delimiter + Scenario: Using as_tree with an alternate delimiter + Given I have a bucket with the following keys: + | key | + | videos-wedding.mkv | + | videos-vacation.mkv | + | photos-2009-family.jpg | + | photos-2009-friends.jpg | + | photos-2010-family.jpg | + | README | + And I use "-" as the delimiter + When I access the bucket as a tree + Then I should receive leaf nodes with the following keys: + | key | + | README | + And I should receive branch nodes with the following prefixes: + | prefix | + | photos- | + | videos- | + + @prefix + Scenario: Using as_tree with a prefix + Given I have a bucket with the following keys: + | key | + | videos/wedding.mkv | + | videos/vacation.mkv | + | photos/2009/family.jpg | + | photos/2009/friends.jpg | + | photos/2010/family.jpg | + | README | + And I use "photos/" as the prefix + When I access the bucket as a tree + Then I should receive branch nodes with the following prefixes: + | prefix | + | photos/2009/ | + | photos/2010/ | + + @prefix @append + Scenario: Using as_tree a prefix while relying on append + Given I have a bucket with the following keys: + | key | + | videos/wedding.mkv | + | videos/vacation.mkv | + | photos/2009/family.jpg | + | photos/2009/friends.jpg | + | photos/2010/family.jpg | + | README | + And I use "photos" as the prefix + When I access the bucket as a tree + Then I should receive branch nodes with the following prefixes: + | prefix | + | photos/2009/ | + | photos/2010/ | + + @prefix @append + Scenario: Using as_tree with a prefix but without append + Given I have a bucket with the following keys: + | key | + | abc/mno | + | abc/xyz | + And I use "abc" as the prefix + And I choose to not append the delimiter + When I access the bucket as a tree + Then I should receive branch nodes with the following prefixes: + | prefix | + | abc/ | + + @branch + Scenario: Using as_tree to access leaves from a branch + Given I have a bucket with the following keys: + | key | + | videos/wedding.mkv | + | videos/vacation.mkv | + | photos/2009/family.jpg | + | photos/2009/friends.jpg | + | photos/2010/family.jpg | + | README | + And I use "photos/" as the prefix + When I access the bucket as a tree + Then the branch prefixed "photos/2009/" should have leaves with keys: + | key | + | photos/2009/family.jpg | + | photos/2009/friends.jpg | diff --git a/features/s3/high_level/uploads.feature b/features/s3/high_level/uploads.feature new file mode 100644 index 00000000000..b5996f86016 --- /dev/null +++ b/features/s3/high_level/uploads.feature @@ -0,0 +1,173 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @high_level @objects @uploads +Feature: Uploads + + As a user of the S3 high-level interface + I want to be able to upload data + so that S3 will store it for me + + @put_object + Scenario: Large file + Given the multipart upload threshold is 5mb + And I have a 7mb file + When I write the file to the object "foo" + Then the contents of object "foo" should eventually match the file + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | POST | + | http | uri | /foo?uploads | + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | PUT | + | http | path | /foo | + | header | content-length | 5242880 | + | param | partNumber | 1 | + | param_match | uploadId | .+ | + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | PUT | + | http | path | /foo | + | header | content-length | 2097152 | + | param | partNumber | 2 | + | param_match | uploadId | .+ | + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | POST | + | http | path | /foo | + | param_match | uploadId | .+ | + + @put_object + Scenario: Large file with metadata + Given the multipart upload threshold is 5mb + And I have a 7mb file + When I write the file to the object "foo" with the following metadata: + | color | red | + | shape | circle | + Then the metadata of object "foo" should eventually be: + | color | red | + | shape | circle | + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | POST | + | http | uri | /foo?uploads | + | header | x-amz-meta-color | red | + | header | x-amz-meta-shape | circle | + + Scenario: Start a multipart upload + When I start an upload to object "foo" + Then the result should be an upload object + And the list of uploads for the bucket should include the upload + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | POST | + | http | uri | /foo?uploads | + + Scenario: Upload a part + Given I start an upload to object "foo" + When I add a part to the upload with data "HELLO" + Then the result should be an uploaded part + And the parts list should include the part I uploaded + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | PUT | + | http | path | /foo | + | header | content-length | 5 | + | param | partNumber | 1 | + | param_match | uploadId | .+ | + + Scenario: Complete an upload with parts uploaded using the object + Given I start an upload to object "foo" + And I add a part to the upload with data "HELLO" + When I complete the upload with the parts uploaded using the object + Then the contents of object "foo" should eventually be "HELLO" + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | POST | + | http | path | /foo | + | param_match | uploadId | .+ | + + Scenario: Complete an upload with all parts in S3 + Given I start an upload to object "foo" + And I add a part to the upload with data "HELLO" + And I get the upload again by ID + When I complete the upload with all parts in S3 + Then the contents of object "foo" should eventually be "HELLO" + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | POST | + | http | path | /foo | + | param_match | uploadId | .+ | + + Scenario: Abort a multipart upload + Given I start an upload to object "foo" + When I abort the upload + Then the list of uploads for the bucket should eventually not include the upload I started + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | DELETE | + | http | path | /foo | + | param_match | uploadId | .+ | + + Scenario: List multipart uploads + Given I start an upload to object "foo" + When I ask for the list of uploads for the bucket + Then the result should eventually include the upload I started + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | GET | + | http | uri_match | /?.*&?uploads | + + Scenario: List uploads for an object + Given I start an upload to object "foo" + When I ask for the list of uploads to the object "foo" + Then the result should eventually include the upload I started + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | GET | + | http | path | / | + | param | uploads | | + | param | prefix | foo | + + Scenario: Get a multipart upload by ID + When I ask for the upload to "foo" with ID "abc123" + Then the result should be an upload object + + Scenario: Ask if an upload exists (does not exist) + Given I ask for the upload to "foo" with ID "abc123" + When I ask if the upload exists + Then the result should be false + + Scenario: Ask if an upload exists (exists) + Given I start an upload to object "foo" + When I ask if the upload exists + Then the result should be true + + Scenario: List parts for an upload + Given I start an upload to object "foo" + And I upload the following parts: + | HELLO | + | GOODBYE | + When I ask for the parts list for the upload + Then the parts list should include: + | part number | size | + | 1 | 5 | + | 2 | 7 | + And the last modified date for each part should be within a minute of now + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | GET | + | http | path | /foo | + | param_match | uploadId | .+ | diff --git a/features/s3/high_level/urls.feature b/features/s3/high_level/urls.feature new file mode 100644 index 00000000000..4f8ffe50a41 --- /dev/null +++ b/features/s3/high_level/urls.feature @@ -0,0 +1,61 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @high_level @objects @urls +Feature: Pre-signed URLs + + As a user of the S3 high-level interface + I want to be able to get pre-signed URLs for objects + so that I can deliver my private data securely to a wide variety of clients + + Scenario: Pre-signed GET + Given the object "foo" has the contents "HELLO" + When I generate a pre-signed GET URL for the object "foo" + And I use a regular HTTP client to GET the URL + Then the response body should be "HELLO" + + Scenario: Pre-signed PUT + When I generate a pre-signed PUT URL for the object "foo" + And I use a regular HTTP client to PUT "HELLO" to the URL + Then the contents of object "foo" should eventually be "HELLO" + + Scenario: Pre-signed DELETE + Given the object "foo" has the contents "HELLO" + When I generate a pre-signed DELETE URL for the object "foo" + And I use a regular HTTP client to DELETE the URL + Then The object with key "foo" should eventually not exist + + Scenario: Pre-signed GET overriding response headers + Given the object "foo" has the contents "{}" + When I generate a pre-signed GET URL for the object "foo" with the following parameters: + | response_content_type | application/json | + | response_content_language | en | + | response_expires | tomorrow | + | response_cache_control | foobla | + | response_content_disposition | attachment | + | response_content_encoding | gzip | + And I use a regular HTTP client to GET the URL + Then the response headers should include: + | content-type | application/json | + | content-language | en | + | expires | tomorrow | + | cache-control | foobla | + | content-disposition | attachment | + | content-encoding | gzip | + + Scenario: Public URL for an object + Given the object "foo" has the contents "HELLO" and a :public_read acl + When I ask for the public URL of object "foo" + And I use a regular HTTP client to GET the URL + Then the response body should be "HELLO" diff --git a/features/s3/high_level/versions.feature b/features/s3/high_level/versions.feature new file mode 100644 index 00000000000..6e1d73d7668 --- /dev/null +++ b/features/s3/high_level/versions.feature @@ -0,0 +1,97 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @high_level @versions +Feature: High-Level Bucket and Object Versioning + + As a user of the high-level S3 interface + I want to work with versioned buckets and objects + So that I can store multiple versions of objects + + @bucket + Scenario: A new bucket with no versioning + When I create a new bucket + Then the bucket should not be versioned + And the bucket versioning state should be "unversioned" + + @bucket + Scenario: Enable bucket versioning + Given I create a new bucket + When I enable versioning on the bucket + Then the bucket should be versioned + And the bucket versioning state should be "enabled" + + @bucket + Scenario: Disable bucket versioning + Given I create a new bucket + And I enable versioning on the bucket + When I suspend versioning on the bucket + Then the bucket should not be versioned + And the bucket versioning state should be "suspended" + + @bucket + Scenario: Tracking versioned objects in a bucket + Given I create a new bucket + And I enable versioning on the bucket + When I write "foo1" to the key "bar" + And I delete the object with key "bar" + And I write "foo2" to the key "bar" + Then the bucket should have 3 versions + And the latest bucket version should contain "foo2" + And version 0 should contain "foo2" + And version 1 should be a delete marker + And version 2 should contain "foo1" + + @bucket + Scenario: Deleting versioned objects + Given I create a new bucket + And I enable versioning on the bucket + And I write "foo1" to the key "bar" + And I write "foo2" to the key "bar" + And I write "foo3" to the key "bar" + And I write "foo4" to the key "bar" + When I delete the latest bucket version + Then the bucket should have 3 versions + And the latest bucket version should contain "foo3" + + @object + Scenario: Version from write + Given I create a new bucket + And I enable versioning on the bucket + When I write "foo1" to the key "key1" + Then the result should be the latest version for the key "key1" + + @object + Scenario: Tracking object versions + Given I create a new bucket + And I enable versioning on the bucket + When I write "foo1" to the key "key1" + And I delete the object with key "key1" + And I write "foo2" to the key "key1" + And I write "bar1" to the key "key2" + And I write "bar2" to the key "key2" + Then the bucket should have 5 versions + And the object "key1" should have 3 versions + And version 0 should contain "foo2" + And version 1 should be a delete marker + And version 2 should contain "foo1" + +# Scenario: Tracking object versions +# Given I create a new bucket +# And I enable versioning on the bucket +# And I write "foo1" to the key "bar" +# And I write "foo2" to the key "bar" +# And I write "foo3" to the key "bar" +# When I get the latest object version +# Then I should get the same version by version_id diff --git a/features/s3/low_level/bucket_acls.feature b/features/s3/low_level/bucket_acls.feature new file mode 100644 index 00000000000..08edf397097 --- /dev/null +++ b/features/s3/low_level/bucket_acls.feature @@ -0,0 +1,60 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @low_level @bucket_acl +Feature: Bucket ACLs + As a user of the S3 low-level client + I want to be able to manipulate bucket ACLs + So that I can control access to my S3 resources + + @set + Scenario: Set bucket ACL + Given I call create_bucket + When I ask the client to set a bucket ACL + Then the result should be a successful response + And the client should have made a "PUT" request to the bucket + And the bucket ACL should resemble the one that was set + + @set + Scenario: Set bucket ACL (hashes) + Given I call create_bucket + When I ask the client to set a bucket ACL using an AccessControlList object + Then the result should be a successful response + And the client should have made a "PUT" request to the bucket + And the bucket ACL should resemble the one that was set + + @set @round_trip + Scenario: Modify bucket ACL with OO interface + Given I call create_bucket + When I add a grant to the bucket ACL + And I ask the client to set the modified bucket ACL + Then the result should be a successful response + And the client should have made a "PUT" request to the bucket + And the bucket ACL should include the new grant + + @get + Scenario: Get bucket ACL + Given I call create_bucket + When I ask the client to get the bucket ACL + Then the result should be a successful response + And the client should have made a "GET" request to the bucket + And the result should return something that looks like a acl from its acl method + + @get + Scenario: Get bucket ACL (OO interface) + Given I call create_bucket + When I ask the client to get the bucket ACL + Then the result should be a successful response + And the client should have made a "GET" request to the bucket + And the result should be an AccessControlList object containing the right data diff --git a/features/s3/low_level/buckets.feature b/features/s3/low_level/buckets.feature new file mode 100644 index 00000000000..3fe5f0f8540 --- /dev/null +++ b/features/s3/low_level/buckets.feature @@ -0,0 +1,72 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @low_level @buckets +Feature: Working with Buckets + + As a user of the S3 low-level client + I want to be able to perform basic CRUD operations on buckets + + @create_bucket + Scenario: Create a bucket + When I call create_bucket + Then the result should be a successful response + And the bucket should exist + And the client should have made a PUT request to the bucket + + @create_bucket + Scenario: Invalid bucket name + When I ask the client to create a bucket with an invalid name + Then the client should raise an argument error + And the client should not make any requests + + # this should raise an error instead of actually creating the bucket + @create_bucket + Scenario: DNS-incompatible bucket name + When I ask the client to create a dns incompatible bucket + Then the bucket should exist + Then a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | PUT | + | http | host | s3.amazonaws.com | + | http | uri_match | /ruby_integration_test_\d+ | + + @create_bucket @endpoint + Scenario: Create a bucket in a different region + When I ask the client to create a bucket in "s3-us-west-1.amazonaws.com" + Then a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | PUT | + | http | host_match | ruby-integration-test-.*.s3-us-west-1.amazonaws.com | + + @delete_bucket + Scenario: Delete bucket + Given I call create_bucket + When I ask the client to delete the bucket + Then the result should be a successful response + And the bucket should not exist + And the client should have made a DELETE request to the bucket + + @delete_bucket + Scenario: Delete bucket that does not exist + When I ask the client to delete a bucket that does not exist + Then the client should raise a client error + And the client should have made a DELETE request to the bucket + + @list_buckets + Scenario: List buckets + Given I call create_bucket + When I ask the client to list buckets + Then the bucket should be in the response + And the client should have made a GET request to the service diff --git a/features/s3/low_level/object_acls.feature b/features/s3/low_level/object_acls.feature new file mode 100644 index 00000000000..77763a062ed --- /dev/null +++ b/features/s3/low_level/object_acls.feature @@ -0,0 +1,60 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @low_level @object_acl +Feature: Object ACLs + As a user of the S3 low-level client + I want to be able to manipulate object ACLs + So that I can control access to my S3 resources + + @set + Scenario: Set object ACL + Given my account has an object in it + When I ask the client to set an object ACL + Then the result should be a successful response + And the client should have made a "PUT" request to the object ACL + And the object ACL should resemble the one that was set + + @set + Scenario: Set object ACL (hashes) + Given my account has an object in it + When I ask the client to set an object ACL using an AccessControlList object + Then the result should be a successful response + And the client should have made a "PUT" request to the object ACL + And the object ACL should resemble the one that was set + + @set @round_trip + Scenario: Modify object ACL with OO interface + Given my account has an object in it + When I add a grant to the object ACL + And I ask the client to set the modified object ACL + Then the result should be a successful response + And the client should have made a "PUT" request to the object ACL + And the object ACL should include the new grant + + @get + Scenario: Get object ACL + Given my account has an object in it + When I ask the client to get the object ACL + Then the result should be a successful response + And the client should have made a "GET" request to the object ACL + And the result should return something that looks like a acl from its acl method + + @get + Scenario: Get object ACL (OO interface) + Given my account has an object in it + When I ask the client to get the object ACL + Then the result should be a successful response + And the client should have made a "GET" request to the object ACL + And the result should be an AccessControlList object containing the right data diff --git a/features/s3/low_level/policies.feature b/features/s3/low_level/policies.feature new file mode 100644 index 00000000000..6f092d52788 --- /dev/null +++ b/features/s3/low_level/policies.feature @@ -0,0 +1,61 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @low_level @bucket_policy +Feature: Bucket Policies + As a user of the S3 low-level client + I want to be able to manipulate bucket policies + So that I can control access to my S3 resources + + @set + Scenario: Set bucket policy + Given I call create_bucket + When I ask the client to set a bucket policy + Then the result should be a successful response + And the client should have made a "PUT" request to the bucket + + @set + Scenario: Set bucket policy (OO interface) + Given I call create_bucket + When I ask the client to set a bucket policy using the OO interface + Then the result should be a successful response + And the client should have made a "PUT" request to the bucket + And the bucket policy should resemble the one I set + + @get @roundtrip + Scenario: Change bucket policy + Given I call create_bucket + And the bucket has a policy + When I produce a modified bucket policy using the OO interface + And I ask the client to set the modified policy + Then the result should be a successful response + And the bucket policy should resemble the one I set + + @get + Scenario: Get bucket policy + Given I call create_bucket + And the bucket has a policy + When I ask the client to get the bucket policy + Then the result should be a successful response + And the client should have made a "GET" request to the bucket + And the result should have the policy in its policy method + + @delete + Scenario: Delete bucket policy + Given I call create_bucket + And the bucket has a policy + When I ask the client to delete the bucket policy + Then the result should be a successful response + And the client should have made a "DELETE" request to the bucket + And the policy should not exist diff --git a/features/s3/low_level/step_definitions/acls.rb b/features/s3/low_level/step_definitions/acls.rb new file mode 100644 index 00000000000..6d5baaf7bf8 --- /dev/null +++ b/features/s3/low_level/step_definitions/acls.rb @@ -0,0 +1,88 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I ask the client to set a bucket ACL$/ do + @result = @s3_client.set_bucket_acl(:bucket_name => @bucket_name, + :acl => (@acl = < + + #{@test_config["owner_id"]} + #{@test_config["display_name"]} + + + + + d25639fbe9c19cd30a4c0f43fbf00e2d3f96400a9aa8dabfbbebe19069d1a5df + Mickey Mouse + + FULL_CONTROL + + + +END +end + +When /^I ask the client to set a bucket ACL using an AccessControlList object$/ do + acl = { + :owner => { + :id => @test_config["owner_id"], + :display_name => @test_config["display_name"] + }, + :grants => [{ :grantee => { + :canonical_user_id => "d25639fbe9c19cd30a4c0f43fbf00e2d3f96400a9aa8dabfbbebe19069d1a5df", + :display_name => "Mickey Mouse" + }, + :permission => :full_control }] + } + @result = @s3_client.set_bucket_acl(:bucket_name => @bucket_name, + :acl => acl) +end + +Then /^the bucket ACL should resemble the one that was set$/ do + doc = REXML::Document.new(@s3_client.get_bucket_acl(:bucket_name => @bucket_name).acl.to_s) + doc.elements["//Grant/Grantee"].to_s.should =~ /d25639fbe9c19cd30a4c0f43fbf00e2d3f96400a9aa8dabfbbebe19069d1a5df/ +end + +When /^I ask the client to get the bucket ACL$/ do + @result = @s3_client.get_bucket_acl(:bucket_name => @bucket_name) +end + +Then /^the result should return something that looks like a acl from its acl method$/ do + @result.acl.to_s.should =~ /AccessControlPolicy/ +end + +Then /^the result should be an AccessControlList object containing the right data$/ do + @result.acl.should be_an(S3::AccessControlList) + @result.acl.owner.id.should == @test_config["owner_id"] + @result.acl.grants.size.should == 1 + grant = @result.acl.grants.first + grant.grantee.canonical_user_id.should == @test_config["owner_id"] + grant.permission.name.should == :full_control +end + +When /^I add a grant to the bucket ACL$/ do + @acl = @s3_client.get_bucket_acl(:bucket_name => @bucket_name).acl + @acl.grant(:read_acp).to(:amazon_customer_email => "aws-dr-sandbox@amazon.com") +end + +When /^I ask the client to set the modified bucket ACL$/ do + @result = @s3_client.set_bucket_acl(:bucket_name => @bucket_name, :acl => @acl) +end + +Then /^the bucket ACL should include the new grant$/ do + acl = @s3_client.get_bucket_acl(:bucket_name => @bucket_name).acl + # it would be nice to check the grantee as well, but S3 + # canonicalizes it so we get something different than what we put in + acl.grants.map { |g| g.permission.name }.should include(:read_acp) +end diff --git a/features/s3/low_level/step_definitions/buckets.rb b/features/s3/low_level/step_definitions/buckets.rb new file mode 100644 index 00000000000..f0fc62945df --- /dev/null +++ b/features/s3/low_level/step_definitions/buckets.rb @@ -0,0 +1,146 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +include AWS + +def get_bucket + Net::HTTP.get(URI.parse("http://#{@endpoint}/#{@bucket_name}/")) +end + +When /^I call create_bucket( asynchronously)?$/ do |async| + create_bucket_low_level(:async => !async.to_s.strip.empty?) +end + +When /^I ask the client to( asynchronously)? create a bucket named "([^\"]*)"$/ do |async, bucket_name| + async = !async.to_s.strip.empty? + create_bucket_low_level(:bucket_name => bucket_name, :async => async) +end + +When /^I ask the client to( asynchronously)? create a bucket with an invalid name$/ do |async| + begin + async = !async.to_s.strip.empty? + create_bucket_low_level(:bucket_name => '$!%%', :async => async) + rescue StandardError => e + @error = e + end +end + +When /^I ask the client to create a bucket in "([^"]*)"$/ do |endpoint| + pending + create_bucket_low_level(:endpoint => endpoint) +end + +When /^I ask the client to create a dns incompatible bucket$/ do + bucket_name = "ruby_integration_test_#{Time.now.to_i}" + create_bucket_low_level(:bucket_name => bucket_name) +end + +Then /^the result should be a successful response$/ do + @result.should be_successful +end + +Then /^the bucket should exist( on completion)?$/ do |async| + unless async.to_s.strip.empty? + complete = false + @result.on_complete { complete = true } + sleep 0.1 until complete + end + sleep 0.5 + resp = get_bucket + resp.should_not =~ /NoSuchBucket/ +end + +Then /^the client should have made a PUT request to the bucket$/ do + r = @http_handler.requests_made.first + r.http_method.should == "PUT" + r.host.should == "#@bucket_name.s3.amazonaws.com" +end + +Then /^the client should raise an argument error$/ do + @error.should be_a(ArgumentError) +end + +Then /^the client should not make any requests$/ do + @http_handler.requests_made.should be_empty if @http_handler.requests_made +end + +Then /^the client should have made a PUT request to the bucket\'s path in S3$/ do + @http_handler.requests_made.should have_at_least(1).item + r = @http_handler.requests_made.first + r.http_method.should == "PUT" + r.host.should == "s3.amazonaws.com" +end + +When /^I ask the client to( asynchronously)? delete the bucket$/ do |async| + @result = @s3_client.delete_bucket(:bucket_name => @bucket_name, :async => (!async.to_s.strip.empty?)) +end + +Then /^the bucket should not exist( on completion)?$/ do |async| + unless async.to_s.strip.empty? + complete = false + @result.on_complete { complete = true } + sleep 0.1 until complete + end + sleep 0.5 + resp = get_bucket + resp.should =~ /NoSuchBucket/ +end + +Then /^the client should have made a DELETE request to the bucket$/ do + @http_handler.requests_made.should have_at_least(1).item + r = @http_handler.requests_made.last + r.http_method.should == "DELETE" + r.host.should == "#@bucket_name.s3.amazonaws.com" +end + +When /^I ask the client to( asynchronously)? delete a bucket that does not exist$/ do |async| + begin + @bucket_name = "ruby-integration-test-#{Time.now.to_i}" + @result = + @s3_client.delete_bucket(:bucket_name => @bucket_name, :async => (!async.to_s.strip.empty?)) + rescue => e + @error = e + end +end + +Then /^the client should raise a client error$/ do + @error.should be_a(Errors::ClientError) +end + +When /^I ask the client to( asynchronously)? list buckets$/ do |async| + @result = @s3_client.list_buckets(:async => (!async.to_s.strip.empty?)) +end + +Then /^the bucket should be in the response( on completion)?$/ do |async| + unless async.to_s.strip.empty? + complete = false + @result.on_complete { complete = true } + sleep 0.1 until complete + end + @result.buckets.should have_at_least(1).item + @result.buckets.map { |b| b.name }.should include(@bucket_name) +end + +Then /^the client should have made a GET request to the service$/ do + @http_handler.requests_made.should have_at_least(1).item + r = @http_handler.requests_made.last + r.http_method.should == "GET" + r.host.should == "s3.amazonaws.com" +end + +Then /^the client should have made a "([^\"]*)" request to the bucket$/ do |verb| + @http_handler.requests_made.should have_at_least(1).item + r = @http_handler.requests_made.last + r.http_method.should == verb + r.host.should == "#@bucket_name.s3.amazonaws.com" +end diff --git a/features/s3/low_level/step_definitions/endpoints.rb b/features/s3/low_level/step_definitions/endpoints.rb new file mode 100644 index 00000000000..67a4c59d01a --- /dev/null +++ b/features/s3/low_level/step_definitions/endpoints.rb @@ -0,0 +1,14 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + + diff --git a/features/s3/low_level/step_definitions/object_acls.rb b/features/s3/low_level/step_definitions/object_acls.rb new file mode 100644 index 00000000000..56fe016a302 --- /dev/null +++ b/features/s3/low_level/step_definitions/object_acls.rb @@ -0,0 +1,99 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Given /^my account has an object in it$/ do + Given "I call create_bucket" + @object_key = "foo" + @s3_client.put_object(:bucket_name => @bucket_name, + :key => @object_key, + :data => "FOO") +end + +When /^I ask the client to set an object ACL$/ do + @result = @s3_client.set_object_acl(:bucket_name => @bucket_name, + :key => @object_key, + :acl => (@acl = < + + #{@test_config["owner_id"]} + #{@test_config["display_name"]} + + + + + 154b2f3550127d0439dfe1e89a03a7a4178048cc05c6fdaeb40796841a5cfcef + Mickey Mouse + + FULL_CONTROL + + + +END +end + +When /^I ask the client to set an object ACL using an AccessControlList object$/ do + acl = { + :owner => { + :id => @test_config["owner_id"], + :display_name => @test_config["display_name"] + }, + :grants => [{ :grantee => { + :canonical_user_id => + "154b2f3550127d0439dfe1e89a03a7a4178048cc05c6fdaeb40796841a5cfcef", + :display_name => "Mickey Mouse" + }, + :permission => :full_control }] + } + @result = @s3_client.set_object_acl(:bucket_name => @bucket_name, + :key => @object_key, + :acl => acl) +end + +Then /^the client should have made a "([^\"]*)" request to the object ACL$/ do |method| + r = @http_handler.requests_made.last + r.http_method.should == method + r.host.should == "#@bucket_name.s3.amazonaws.com" + r.uri.should == "/#@object_key?acl" +end + +Then /^the object ACL should resemble the one that was set$/ do + doc = REXML::Document.new(@s3_client.get_object_acl(:bucket_name => @bucket_name, + :key => @object_key).acl.to_s) + doc.elements["//Grant/Grantee"].to_s.should =~ /154b2f3550127d0439dfe1e89a03a7a4178048cc05c6fdaeb40796841a5cfcef/ +end + +When /^I add a grant to the object ACL$/ do + @acl = @s3_client.get_object_acl(:bucket_name => @bucket_name, + :key => @object_key).acl + @acl.grant(:read_acp).to(:amazon_customer_email => "aws-dr-sandbox@amazon.com") +end + +When /^I ask the client to set the modified object ACL$/ do + @result = @s3_client.set_object_acl(:bucket_name => @bucket_name, + :key => @object_key, + :acl => @acl) +end + +Then /^the object ACL should include the new grant$/ do + acl = @s3_client.get_object_acl(:bucket_name => @bucket_name, + :key => @object_key).acl + # it would be nice to check the grantee as well, but S3 + # canonicalizes it so we get something different than what we put in + acl.grants.map { |g| g.permission.name }.should include(:read_acp) +end + +When /^I ask the client to get the object ACL$/ do + @result = @s3_client.get_object_acl(:bucket_name => @bucket_name, + :key => @object_key) +end diff --git a/features/s3/low_level/step_definitions/objects.rb b/features/s3/low_level/step_definitions/objects.rb new file mode 100644 index 00000000000..4ed10f19b02 --- /dev/null +++ b/features/s3/low_level/step_definitions/objects.rb @@ -0,0 +1,20 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I call put_object to "([^\"]*)" with "([^\"]*)"$/ do |key,data| + @s3_client.put_object( + :bucket_name => @bucket_name, + :key => key, + :data => data + ) +end diff --git a/features/s3/low_level/step_definitions/policies.rb b/features/s3/low_level/step_definitions/policies.rb new file mode 100644 index 00000000000..ccc16a56153 --- /dev/null +++ b/features/s3/low_level/step_definitions/policies.rb @@ -0,0 +1,109 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +def valid_policy; < @bucket_name, :policy => valid_policy) +end + +Given /^the bucket has a policy$/ do + @s3_client.set_bucket_policy(:bucket_name => @bucket_name, :policy => valid_policy) +end + +Then /^the result should have the policy in its policy method$/ do + @result.policy.statements.size.should == 1 + stmt = @result.policy.statements.first + stmt.sid.should == "Stmt1296685809629" + stmt.effect.should == "Allow" + stmt.resources.join.should include(@bucket_name) + stmt.principals.join.should include("681294939609") +end + +When /^I ask the client to get the bucket policy$/ do + @result = @s3_client.get_bucket_policy(:bucket_name => @bucket_name) +end + +When /^I ask the client to delete the bucket policy$/ do + @result = @s3_client.delete_bucket_policy(:bucket_name => @bucket_name) +end + +Then /^the policy should not exist$/ do + lambda do + @s3_client.get_bucket_policy(:bucket_name => @bucket_name) + end.should raise_error(S3::Errors::NoSuchBucketPolicy) +end + +When /^I ask the client to set a bucket policy using the OO interface$/ do + @result = @s3_client.set_bucket_policy(:bucket_name => @bucket_name, + :policy => S3::Policy.new do |policy| + policy.allow(:actions => "s3:*", + :resources => @bucket_name, + :principals => "681294939609"). + where("aws:SourceIp").is_ip_address("192.168.1.0/24", + "192.168.1.1/32"). + where("aws:SourceIp").not_ip_address("192.168.1.2/32"). + where("s3:prefix").like("photos/*"). + where("aws:SecureTransport").is(true) + end) +end + +Then /^the bucket policy should resemble the one I set$/ do + policy = @s3_client.get_bucket_policy(:bucket_name => @bucket_name).policy + policy.statements.map { |s| s.resources }. + flatten.should include("arn:aws:s3:::#@bucket_name") + policy.statements.inject(0) { |sum, s| sum + s.conditions.to_h.size }.should > 0 + stmt = policy.statements.find do |s| + s.resources.include?("arn:aws:s3:::#@bucket_name") and + !s.conditions.to_h.empty? + end + stmt.principals.join.should =~ /681294939609/ + ip_conditions = stmt.conditions[:source_ip] + ip_conditions.operators.sort.should == ["IpAddress", "NotIpAddress"] + ip_conditions[:is_ip_address].values.sort.should == ["192.168.1.0/24", "192.168.1.1/32"] + stmt.conditions["StringLike"]["s3:prefix"].values.should == ["photos/*"] + stmt.conditions[:is].operators.should == ["Bool"] + stmt.conditions[:is][:secure_transport].values.should == [true] +end + +When /^I produce a modified bucket policy using the OO interface$/ do + @policy = @s3_client.get_bucket_policy(:bucket_name => @bucket_name).policy + @policy.allow(:actions => "s3:*", + :resources => @bucket_name, + :principals => "681294939609"). + where("aws:SourceIp").is_ip_address("192.168.1.0/24", + "192.168.1.1/32"). + where("aws:SourceIp").not_ip_address("192.168.1.2/32"). + where("s3:prefix").like("photos/*"). + where("aws:SecureTransport").is(true) +end + +When /I ask the client to set the modified policy/ do + @result = @s3_client.set_bucket_policy(:bucket_name => @bucket_name, + :policy => @policy) +end diff --git a/features/s3/low_level/step_definitions/uploads.rb b/features/s3/low_level/step_definitions/uploads.rb new file mode 100644 index 00000000000..a228a82f5c0 --- /dev/null +++ b/features/s3/low_level/step_definitions/uploads.rb @@ -0,0 +1,92 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I ask the client to initiate a multipart upload to "([^\"]*)"$/ do |key| + @result = @s3_client.initiate_multipart_upload(:bucket_name => @bucket_name, + :key => key) + @upload_id = @result.upload_id + @etags = {} +end + +Then /^the result should contain an upload ID$/ do + @result.should respond_to(:upload_id) + @result.upload_id.should_not be_empty +end + +When /^I ask the client to list the multipart uploads in the bucket$/ do + @result = @s3_client.list_multipart_uploads(:bucket_name => @bucket_name) +end + +Then /^the result should include the upload ID I initiated$/ do + @result.uploads.map { |upload| upload.upload_id }.should include(@upload_id) +end + +When /^I ask the client to upload a (\d+)(mb|kb) part for "([^\"]*)" containing "([^\"]*)" as part number (\d+)$/ do |size, unit, key, contents, number| + bytes = size.to_i * 1024 + bytes *= 1024 if unit == "mb" + data = "!"*bytes + data = data[0..(-1 * (contents.length + 1))] + contents + @result = @s3_client.upload_part(:bucket_name => @bucket_name, + :key => key, + :upload_id => @upload_id, + :data => data, + :part_number => number) + @etags[number] = @result.etag +end + +When /^I ask the client to complete the upload for "([^\"]*)"$/ do |key| + @result = @s3_client.complete_multipart_upload(:bucket_name => @bucket_name, + :key => key, + :upload_id => @upload_id, + :parts => @etags.map do |(part, etag)| + { :part_number => part, + :etag => etag } + end) +end + +Then /^the object "([^\"]*)" should eventually have a body including the following strings:$/ do |key, table| + sleep 1 + body = @s3_client.get_object(:bucket_name => @bucket_name, + :key => key).data + + table.raw.flatten.each do |str| + body.should include(str) + end +end + +When /^I ask the client to abort the upload to "([^\"]*)"$/ do |key| + @result = @s3_client.abort_multipart_upload(:bucket_name => @bucket_name, + :key => key, + :upload_id => @upload_id) +end + +Then /^the upload should eventually not exist$/ do + sleep 1 + @s3_client.list_multipart_uploads(:bucket_name => @bucket_name). + uploads.map { |u| u.upload_id }. + should_not include(@upload_id) +end + +When /^I ask the client to list parts for the upload to "([^\"]*)"$/ do |key| + @result = @s3_client.list_parts(:bucket_name => @bucket_name, + :key => key, + :upload_id => @upload_id) +end + +Then /^the result should include a part number (\d+) that is (\d+)(mb|kb) in size$/ do |part_number, size, unit| + bytes = size.to_i * 1024 + bytes *= 1024 if unit == "mb" + @result.parts.map { |p| p.part_number }.should include(part_number.to_i) + @result.parts.find { |p| p.part_number == part_number.to_i }. + size.should == bytes +end diff --git a/features/s3/low_level/step_definitions/versions.rb b/features/s3/low_level/step_definitions/versions.rb new file mode 100644 index 00000000000..68ce08c9d57 --- /dev/null +++ b/features/s3/low_level/step_definitions/versions.rb @@ -0,0 +1,28 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Given /^ my account has a versioned bucket in it$/ do + Given "I call create_bucket" + And "I enable versioning for the bucket" +end + +When /^I call set_bucket_versioning with "([^\"]*)"$/ do |state| + @s3_client.set_bucket_versioning( + :bucket_name => @bucket_name, + :state => state.to_sym) +end + +Then /^get_bucket_versioning should return "([^\"]*)"$/ do |status| + status = @s3_client.get_bucket_versioning(:bucket_name => @bucket_name).status + status.should == status.to_sym +end diff --git a/features/s3/low_level/uploads.feature b/features/s3/low_level/uploads.feature new file mode 100644 index 00000000000..dbb406d7d18 --- /dev/null +++ b/features/s3/low_level/uploads.feature @@ -0,0 +1,84 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @low_level @objects @uploads +Feature: Multi-part Uploads + As a user of the S3 low-level client + I want to be able to perform multi-part uploads + So that I can reliably and efficiently upload large objects + + @high_bandwidth + Scenario: Upload with one part + When I ask the client to initiate a multipart upload to "foo" + And I ask the client to upload a 5mb part for "foo" containing "FIRST" as part number 1 + And I ask the client to upload a 1kb part for "foo" containing "SECOND" as part number 2 + And I ask the client to complete the upload for "foo" + Then the result should be a successful response + And the object "foo" should eventually have a body including the following strings: + | FIRST | + | SECOND | + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | POST | + | http | uri | /foo?uploads | + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | PUT | + | http | path | /foo | + | param | partNumber | 1 | + | param_match | uploadId | .+ | + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | PUT | + | http | path | /foo | + | param | partNumber | 2 | + | param_match | uploadId | .+ | + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | POST | + | http | path | /foo | + | param_match | uploadId | .+ | + + Scenario: List all multipart uploads + Given I ask the client to initiate a multipart upload to "foo" + When I ask the client to list the multipart uploads in the bucket + Then the result should be a successful response + And the result should include the upload ID I initiated + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | GET | + | http | uri | /?uploads | + + Scenario: Abort an upload + Given I ask the client to initiate a multipart upload to "foo" + When I ask the client to abort the upload to "foo" + Then the result should be a successful response + And the upload should eventually not exist + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | DELETE | + | http | path | /foo | + | param_match | uploadId | .+ | + + Scenario: List parts + Given I ask the client to initiate a multipart upload to "foo" + And I ask the client to upload a 1kb part for "foo" containing "HELLO" as part number 1 + When I ask the client to list parts for the upload to "foo" + Then the result should be a successful response + And the result should include a part number 1 that is 1kb in size + And a request should have been made like: + | TYPE | NAME | VALUE | + | http | verb | GET | + | http | path | /foo | + | param_match | uploadId | .+ | diff --git a/features/s3/low_level/versions.feature b/features/s3/low_level/versions.feature new file mode 100644 index 00000000000..1ee1d02b236 --- /dev/null +++ b/features/s3/low_level/versions.feature @@ -0,0 +1,38 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @low_level @versions +Feature: Low-Level Bucket and Object Versioning + + As a user of the low-level S3 client + I want to enable and suspend versioning on buckets + So that I can store multiple versions of objects + + @buckets @unversioned + Scenario: Bucket with versioning never enabled + Given I call create_bucket + Then get_bucket_versioning should return "unversioned" + + @buckets @enable + Scenario: Enable bucket versioning + Given I call create_bucket + When I call set_bucket_versioning with "enabled" + Then get_bucket_versioning should return "enabled" + + @buckets @suspend + Scenario: Suspend bucket versioning + Given I call create_bucket + And I call set_bucket_versioning with "enabled" + When I call set_bucket_versioning with "suspended" + Then get_bucket_versioning should return "suspended" diff --git a/features/s3/step_definitions/async_buckets.rb b/features/s3/step_definitions/async_buckets.rb new file mode 100644 index 00000000000..3f75c3d59ae --- /dev/null +++ b/features/s3/step_definitions/async_buckets.rb @@ -0,0 +1,28 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Then /^the result should be a response handle$/ do + @result.should be_kind_of AWS::AsyncHandle +end + +Then /^a client error should be available on failure$/ do + complete = false + status = nil + @result.on_complete do |in_status| + status = in_status + complete = true + end + sleep 0.1 until complete + status.should == :failure + @result.error.should be_a(Errors::ClientError) +end diff --git a/features/simple_db/high_level/count.feature b/features/simple_db/high_level/count.feature new file mode 100644 index 00000000000..970d6d9d2bf --- /dev/null +++ b/features/simple_db/high_level/count.feature @@ -0,0 +1,61 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@simple_db @high_level @items @count +Feature: SimpleDB Count Items + + As a user of SimpleDB + I want to count items + + Scenario: Count items + Given I have 3 items in a domain + When I count the items in the domain + Then the result should be 3 + And a select should have been performed like: + | PART | VALUE | + | output_list | count(*) | + + Scenario Outline: Count items with a limit + Given I have items in a domain + When I count the items in the domain with limit + Then the result should be + And a select should have been performed like: + | PART | VALUE | + | limit | | + + Examples: + | count | limit | result | + | 5 | 3 | 3 | + | 2 | 3 | 2 | + + Scenario: Count items with conditions + Given I add the following attributes to "car" + | name | value | + | size | small | + And I add the following attributes to "semi truck" + | name | value | + | size | large | + When I count the items in the domain where "size" is "large" + Then the result should be 1 + + @wip + Scenario: Count items with order + Given I add the following attributes to "car" + | name | value | + | size | small | + And I add the following attributes to "semi truck" + | name | value | + | size | large | + When I count the items in the domain ordering by size + Then the result should be 2 diff --git a/features/simple_db/high_level/domains.feature b/features/simple_db/high_level/domains.feature new file mode 100644 index 00000000000..59d011ed724 --- /dev/null +++ b/features/simple_db/high_level/domains.feature @@ -0,0 +1,50 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@simple_db @domains @high_level +Feature: SimpleDB High-Level Domains + + As a user of SimpleDB + I want a high-level interface to work with domains + + @create_domain + Scenario: creating a domain + When I create a domain named "new_domain" + Then The domain should be named "new_domain" + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | CreateDomain | + | param | DomainName | new_domain | + + @delete_domain + Scenario: deleting a domain + Given I create a domain named "to_delete" + When I get the domain name "to_delete" + And I delete the domain + Then The domain should not exist + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DeleteDomain | + | param | DomainName | to_delete | + + @list_domains + Scenario: listing domains + Given I create a domain named "needle" + When I enumerate domains + Then A domain named "needle" should be in the list + + @domain_metadata + Scenario: describing domains + Given I create a domain named "for_metadata" + Then I can get metadata from the domain diff --git a/features/simple_db/high_level/errors.feature b/features/simple_db/high_level/errors.feature new file mode 100644 index 00000000000..53b0ecfec39 --- /dev/null +++ b/features/simple_db/high_level/errors.feature @@ -0,0 +1,28 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@simple_db @errors +Feature: SimpleDB modeled errors + + I want to be able to rescue named errors. + + Scenario Outline: Rescue NoSuchDomain + When I get the domain name "does_not_exist" + And I delete the domain rescuing "" + Then I should rescue the error with code "" + + Examples: + | class | code | + | SimpleDB::Errors::NoSuchDomain | NoSuchDomain | + | Errors::ClientError | NoSuchDomain | diff --git a/features/simple_db/high_level/items.feature b/features/simple_db/high_level/items.feature new file mode 100644 index 00000000000..35f1f7e0f10 --- /dev/null +++ b/features/simple_db/high_level/items.feature @@ -0,0 +1,333 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@simple_db @high_level @items +Feature: SimpleDB Items + + As a user of SimpleDB + I want to work with items + + Scenario: Getting attributes for a non existant item + When I get the "colors" attribute for "car" + Then the attribute should have no values + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | GetAttributes | + | param | ItemName | car | + | param | AttributeName.1 | colors | + + Scenario: Adding values one at a time + When I add the value "red" to the "colors" attribute of "car" + And I add the value "green" to the "colors" attribute of "car" + Then the "colors" attribute of "car" item should eventually be: + | value | + | red | + | green | + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Attribute.1.Value | red | + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Attribute.1.Value | green | + + @conditional + Scenario: Adding values one at a time with an expected value + Given I add the value "red" to the "colors" attribute of "car" + And I add the value "baz" to the "foo" attribute of "car" + When I add the value "green" to the "colors" attribute of "car" if "foo" is "bar" + Then the "colors" attribute of "car" item should eventually be: + | value | + | red | + And I should get a "ConditionalCheckFailed" error + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Attribute.1.Value | green | + | param | Expected.1.Name | foo | + | param | Expected.1.Value | bar | + + @conditional + Scenario: Adding values one at a time with an expected no value for an attribute + Given I add the value "red" to the "colors" attribute of "car" + And I add the value "bar" to the "foo" attribute of "car" + When I add the value "green" to the "colors" attribute of "car" unless "foo" has a value + Then the "colors" attribute of "car" item should eventually be: + | value | + | red | + And I should get a "ConditionalCheckFailed" error + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Attribute.1.Value | green | + | param | Expected.1.Name | foo | + | param | Expected.1.Exists | false | + + Scenario: Adding multiple values at the same time + When I add the following values to the "colors" attribute of "car" + | value | + | red | + | green | + Then the "colors" attribute of "car" item should eventually be: + | value | + | red | + | green | + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Attribute.1.Value | red | + | param | Attribute.2.Value | green | + + Scenario: Replacing values on a single attribute + Given I add the value "red" to the "colors" attribute of "car" + And I add the value "green" to the "colors" attribute of "car" + When I set the "colors" attribute of "car" to "blue": + Then the "colors" attribute of "car" item should eventually be: + | value | + | blue | + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Attribute.1.Name | colors | + | param | Attribute.1.Value | blue | + | param | Attribute.1.Replace | true | + + @conditional + Scenario: Replacing values on a single attribute with an expected value + Given I add the value "red" to the "colors" attribute of "car" + And I add the value "green" to the "colors" attribute of "car" + And I add the value "baz" to the "foo" attribute of "car" + When I set the "colors" attribute of "car" to "blue" if "foo" is "bar": + Then the "colors" attribute of "car" item should eventually be: + | value | + | red | + | green | + And I should get a "ConditionalCheckFailed" error + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Attribute.1.Name | colors | + | param | Attribute.1.Value | blue | + | param | Attribute.1.Replace | true | + | param | Expected.1.Name | foo | + | param | Expected.1.Value | bar | + + @conditional + Scenario: Replacing values on a single attribute expecting no value for an attribute + Given I add the value "red" to the "colors" attribute of "car" + And I add the value "green" to the "colors" attribute of "car" + And I add the value "bar" to the "foo" attribute of "car" + When I set the "colors" attribute of "car" to "blue" unless "foo" has a value: + Then the "colors" attribute of "car" item should eventually be: + | value | + | red | + | green | + And I should get a "ConditionalCheckFailed" error + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | Attribute.1.Name | colors | + | param | Attribute.1.Value | blue | + | param | Attribute.1.Replace | true | + | param | Expected.1.Name | foo | + | param | Expected.1.Exists | false | + + Scenario: Replacing values on multiple attributes + Given I add the following attributes to "car" + | name | value | + | wheels | 4 | + | colors | red | + | colors | green | + | doors | 4 | + When I set the following attributes to "car" + | name | value | + | wheels | 2 | + | colors | white | + Then the attributes for "car" should eventually be: + | name | value | + | doors | 4 | + | colors | white | + | wheels | 2 | + + Scenario: Putting attributes, mixed adds and replacements + Given I add the value "white" to the "colors" attribute of "car" + And I add the value "2" to the "wheels" attribute of "car" + When I put the following attributes to "car" + | name | value | replace | + | wheels | 4 | yes | + | colors | red | no | + | colors | green | no | + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | PutAttributes | + | param | ItemName | car | + | param | Attribute.1.Name | colors | + | param | Attribute.1.Value | red | + | param | Attribute.1.Replace | false | + | param | Attribute.2.Name | colors | + | param | Attribute.2.Value | green | + | param | Attribute.2.Replace | false | + | param | Attribute.3.Name | wheels | + | param | Attribute.3.Value | 4 | + | param | Attribute.3.Replace | true | + Then the attributes for "car" should eventually be: + | name | value | + | colors | green | + | colors | red | + | colors | white | + | wheels | 4 | + + Scenario: Getting attributes for an item (names only) + When I add the value "red" to the "colors" attribute of "car" + And I add the value "green" to the "colors" attribute of "car" + And I add the value "100" to the "price" attribute of "car" + Then the "car" item should eventually have attributes named: + | name | + | colors | + | price | + + Scenario: Deleting an item + Given I add the following values to the "colors" attribute of "car" + | value | + | red | + | green | + When I delete the "car" item + Then the "car" item should eventually have no attributes + + Scenario: Getting a list of all items + When I add the following attributes to "car" + | name | value | + | wheels | 4 | + | colors | red | + | colors | green | + And I add the following attributes to "bike" + | name | value | + | wheels | 2 | + | colors | black | + Then the domain should eventually have the following items: + | name | + | car | + | bike | + + Scenario: Getting all attribute values for an item + When I add the following attributes to "car" + | name | value | + | wheels | 4 | + | colors | red | + | colors | green | + Then the attributes for "car" should eventually be: + | name | value | + | colors | green | + | colors | red | + | wheels | 4 | + + Scenario: Deleting attributes by name + Given I add the following attributes to "car" + | name | value | + | size | small | + | wheels | 4 | + | colors | red | + | colors | green | + When I delete the attributes from "car" named: + | name | + | wheels | + | colors | + Then the attributes for "car" should eventually be: + | name | value | + | size | small | + + Scenario: Deleting attributes by name and value + Given I add the following attributes to "car" + | name | value | + | wheels | 4 | + | colors | red | + | colors | green | + | colors | blue | + | size | small | + When I delete the following attribute values from "car": + | name | value | + | colors | red | + | colors | blue | + | wheels | 7 | + Then the attributes for "car" should eventually be: + | name | value | + | colors | green | + | wheels | 4 | + | size | small | + + @conditional + Scenario: Deleting attributes with an expected value + Given I add the following attributes to "car" + | name | value | + | wheels | 4 | + | colors | red | + | colors | green | + | colors | blue | + | size | small | + When I delete the following attribute values from "car" if "size" is "large": + | name | value | + | colors | red | + | colors | blue | + | wheels | 7 | + Then the attributes for "car" should eventually be: + | name | value | + | colors | blue | + | colors | green | + | colors | red | + | wheels | 4 | + | size | small | + And I should get a "ConditionalCheckFailed" error + + @conditional + Scenario: Deleting attributes expecting an attribute not to exist + Given I add the following attributes to "car" + | name | value | + | wheels | 4 | + | colors | red | + | colors | green | + | colors | blue | + | size | small | + When I delete the following attribute values from "car" if "size" has no value: + | name | value | + | colors | red | + | colors | blue | + | wheels | 7 | + Then the attributes for "car" should eventually be: + | name | value | + | colors | blue | + | colors | green | + | colors | red | + | wheels | 4 | + | size | small | + And I should get a "ConditionalCheckFailed" error + + Scenario: Get all data for an item + Given I add the following attributes to "car" + | name | value | + | size | small | + | wheels | 4 | + | colors | red | + | colors | green | + When I ask for the data for "car" + Then the resulting ItemData should contain the following attribute data: + | name | value | + | size | small | + | wheels | 4 | + | colors | red | + | colors | green | + And 1 request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | GetAttributes | + | param | ItemName | car | diff --git a/features/simple_db/high_level/select.feature b/features/simple_db/high_level/select.feature new file mode 100644 index 00000000000..fa26728d597 --- /dev/null +++ b/features/simple_db/high_level/select.feature @@ -0,0 +1,105 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@simple_db @high_level @items @select +Feature: SimpleDB Items + + As a user of SimpleDB + I want to query items + + Scenario: Query by attribute values hash + When I select items using the following attribute conditions: + | color | red | + | model | mustang | + Then a select should have been performed like: + | PART | VALUE | + | condition | `color` = 'red' AND `model` = 'mustang' | + + Scenario: Query with substitutions + When I select items using the following conditions and substitutions: + | CONDITION | SUBSTITUTION | + | every(color) in ? | ["'red'", "blue"] | + | mileage > ? | 100000 | + Then a select should have been performed like: + | PART | VALUE | + | condition | every(color) in ('''red''', 'blue') AND mileage > '100000' | + + Scenario: Query with named substitutions + When I select items using the following named substitutions: + | CONDITION | color = :color and model = :model | + | color | red | + | model | mustang | + Then a select should have been performed like: + | PART | VALUE | + | condition | color = 'red' and model = 'mustang' | + + Scenario: Query with range + When I select items where "mileage" is between "25000" and "50000" + Then a select should have been performed like: + | PART | VALUE | + | condition | `mileage` BETWEEN '25000' AND '50000' | + + Scenario: Query with sort instruction + When I select items ordering by "mileage" descending + Then a select should have been performed like: + | PART | VALUE | + | condition | `mileage` IS NOT NULL | + | sort_instructions | `mileage` DESC | + + Scenario: Query with limit + When I select all items with a limit of 1 + Then a select should have been performed like: + | PART | VALUE | + | limit | 1 | + + Scenario: Query fetching attribute data + Given I add the following attributes to "item001" + | name | value | + | color | red | + | model | mustang | + | mileage | 50000 | + And I add the following attributes to "item002" + | name | value | + | color | blue | + | model | rav4 | + | mileage | 140000 | + When I select all attributes + Then the result should contain the following attribute data: + | item | attribute | value | + | item001 | color | red | + | item001 | model | mustang | + | item001 | mileage | 50000 | + | item002 | color | blue | + | item002 | model | rav4 | + | item002 | mileage | 140000 | + + Scenario: Enumerating items with batch_size and limit + Given I have 3 items in a domain + When I enumerate items with a limit of 3 and batch size of 2 + Then a select should have been performed like: + | PART | VALUE | + | limit | 2 | + And a select should have been performed like: + | PART | VALUE | + | limit | 1 | + + Scenario: Selecting items with batch_size and limit + Given I have 3 items in a domain + When I select item data with a limit of 3 and batch size of 2 + Then a select should have been performed like: + | PART | VALUE | + | limit | 2 | + And a select should have been performed like: + | PART | VALUE | + | limit | 1 | diff --git a/features/simple_db/high_level/step_definitions/count.rb b/features/simple_db/high_level/step_definitions/count.rb new file mode 100644 index 00000000000..1716160bdcb --- /dev/null +++ b/features/simple_db/high_level/step_definitions/count.rb @@ -0,0 +1,34 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I count the items in the domain$/ do + @result = @domain.items.count(:consistent_read => true) +end + +Then /^the result should be (\d+)$/ do |result| + @result.should == result.to_i +end + +Given /^I count the items in the domain with limit (\d+)$/ do |limit| + @result = @domain.items.limit(limit.to_i).count(:consistent_read => true) +end + +When /^I count the items in the domain where "([^\"]*)" is "([^\"]*)"$/ do |att, value| + @result = @domain.items. + where(Hash[[[att, value]]]). + count(:consistent_read => true) +end + +When /^I count the items in the domain ordering by size$/ do + @result = @domain.items.order(:size).count(:consistent_read => true) +end diff --git a/features/simple_db/high_level/step_definitions/domains.rb b/features/simple_db/high_level/step_definitions/domains.rb new file mode 100644 index 00000000000..c4641b6e53f --- /dev/null +++ b/features/simple_db/high_level/step_definitions/domains.rb @@ -0,0 +1,53 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I create a domain named "([^"]*)"$/ do |domain_name| + create_domain_high_level(domain_name) +end + +When /^I get the domain name "([^"]*)"$/ do |domain_name| + @domain = @sdb.domains[domain_name] +end + +When /^I delete the domain$/ do + @domain.delete +end + +When /^I enumerate domains$/ do + @domains = [] + @sdb.domains.each do |domain| + @domains << domain + end +end + +Then /^I can get metadata from the domain$/ do + @metadata = @domain.metadata + @metadata.should be_a(AWS::SimpleDB::DomainMetadata) +end + +Then /^The domain should be named "([^"]*)"$/ do |domain_name| + @domain.name.should == domain_name +end + +Then /^The domain should exist$/ do + @domain.exists?.should == true +end + +Then /^The domain should not exist$/ do + @domain.exists?.should == false +end + +Then /^A domain named "([^"]*)" should be in the list$/ do |domain_name| + klass = AWS::SimpleDB::Domain + @domains.detect{|domain| domain.name == domain_name }.should be_a(klass) +end diff --git a/features/simple_db/high_level/step_definitions/items.rb b/features/simple_db/high_level/step_definitions/items.rb new file mode 100644 index 00000000000..17065cb7fb1 --- /dev/null +++ b/features/simple_db/high_level/step_definitions/items.rb @@ -0,0 +1,237 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Before("@simple_db", "@high_level", "@items") do + create_domain_high_level +end + +When /^I get the "([^\"]*)" attribute for "([^\"]*)"$/ do |attr_name, item_name| + @attribute = @domain.items[item_name].attributes[attr_name] +end + +Then /^the attribute should have no values$/ do + @attribute.to_a.should == [] +end + +When /^I add the value "([^\"]*)" to the "([^\"]*)" attribute of "([^\"]*)"$/ do |value, attribute_name, item_name| + @domain.items[item_name].attributes[attribute_name].add(value) +end + +Then /^the "([^\"]*)" attribute of "([^\"]*)" item should eventually be:$/ do |attribute_name, item_name, table| + + attribute = @domain.items[item_name].attributes[attribute_name] + + sdb_values = [] + attribute.each(:consistent_read => true) do |value| + sdb_values << value + end + + table_values = table.hashes.collect{|hash| hash['value'] }.sort + table_values.sort.should == sdb_values.sort + +end + +When /^I add the following values to the "([^\"]*)" attribute of "([^\"]*)"$/ do |attribute_name, item_name, table| + values = table.hashes.collect{|hash| hash['value'] } + @domain.items[item_name].attributes[attribute_name].add(values) +end + +When /^I set the "([^\"]*)" attribute of "([^\"]*)" to "([^\"]*)":$/ do |attribute_name,item_name,value| + @domain.items[item_name].attributes[attribute_name].set(value) +end + +Then /^the "([^\"]*)" item should eventually have attributes named:$/ do |item_name,table| + + attributes = @domain.items[item_name].attributes + + names = [] + attributes.each(:consistent_read => true) do |attribute| + names << attribute.name + end + + expected_names = table.hashes.collect{|hash| hash['name'] } + + names.sort.should == expected_names.sort + +end + +When /^I delete the "([^\"]*)" item$/ do |item_name| + @domain.items[item_name].delete +end + +Then /^the "([^\"]*)" item should eventually have no attributes$/ do |item_name| + + found = [] + @domain.items[item_name].attributes.each(:consistent_read => true) do |attribute| + found << attribute + end + found.should == [] + +end + +When /^I (add|set) the following attributes to "([^\"]*)"$/ do |add_or_set, item_name, table| + + attributes_hash = {} + table.hashes.each do |hash| + attributes_hash[hash['name']] ||= [] + attributes_hash[hash['name']] << hash['value'] + end + + @domain.items[item_name].attributes.send(add_or_set, attributes_hash) + +end + +Then /^the domain should eventually have the following items:$/ do |table| + + item_names = [] + @domain.items.each(:consistent_read => true) do |item| + item_names << item.name + end + + expected_item_names = table.hashes.collect{|hash| hash['name'] } + + item_names.sort.should == expected_item_names.sort + +end + +Then /^the attributes for "([^\"]*)" should eventually be:$/ do |item_name, table| + + collection = @domain.items[item_name].attributes + + attributes = [] + collection.each_value(:consistent_read => true) do |attr_name, attr_value| + attributes << { 'name' => attr_name, 'value' => attr_value } + end + + table.diff!(attributes) + +end + +Then /^I delete the attributes from "([^\"]*)" named:$/ do |item_name, table| + attribute_names = table.hashes.collect{|hash| hash['name'] } + @domain.items[item_name].attributes.delete(attribute_names) +end + +When /^I ask for the data for "([^\"]*)"$/ do |item| + @result = @domain.items[item].data(:consistent_read => true) +end + +Then /^the resulting ItemData should contain the following attribute data:$/ do |table| + table.hashes.each do |hash| + @result.attributes.should have_key(hash["name"]) + @result.attributes[hash["name"]].should include(hash["value"]) + end +end + +When /^I delete the following attribute values from "([^\"]*)":$/ do |item_name, table| + delete_opts = table.hashes.inject({}) do |hash, h| + (hash[h["name"].to_sym] ||= []) << h["value"] + hash + end + @domain.items[item_name].attributes. + delete(delete_opts) +end + +When /^I delete the following attribute values from "([^\"]*)" if "([^\"]*)" is "([^\"]*)":$/ do |item_name, expect_att, expect_value, table| + delete_opts = table.hashes.inject({}) do |hash, h| + (hash[h["name"].to_sym] ||= []) << h["value"] + hash + end + if !expect_att.to_s.empty? + delete_opts[:if] = Hash[[[expect_att.to_sym, expect_value]]] + end + begin + @domain.items[item_name].attributes. + delete(delete_opts) + rescue Errors::ClientError => e + @error = e + end +end + +When /^I delete the following attribute values from "([^\"]*)" if "([^\"]*)" has no value:$/ do |item_name, expect_att, table| + delete_opts = table.hashes.inject({}) do |hash, h| + (hash[h["name"].to_sym] ||= []) << h["value"] + hash + end + if !expect_att.to_s.empty? + delete_opts[:unless] = expect_att.to_sym + end + begin + @domain.items[item_name].attributes. + delete(delete_opts) + rescue Errors::ClientError => e + @error = e + end +end + +Then /^I should get a "([^\"]*)" error$/ do |code| + @error.should be_a(SimpleDB::Errors::const_get(code)) +end + +When /^I add the value "([^\"]*)" to the "([^\"]*)" attribute of "([^\"]*)" if "([^\"]*)" is "([^\"]*)"$/ do |value, attribute, item_name, expect_att, expect_value| + begin + @domain.items[item_name].attributes[attribute]. + add(value, :if => Hash[[[expect_att, expect_value]]]) + rescue Errors::ClientError => e + @error = e + end +end + +When /^I add the value "([^\"]*)" to the "([^\"]*)" attribute of "([^\"]*)" unless "([^\"]*)" has a value$/ do |value, attribute, item_name, expect_att| + begin + @domain.items[item_name].attributes[attribute]. + add(value, :unless => expect_att) + rescue Errors::ClientError => e + @error = e + end +end + +When /^I set the "([^\"]*)" attribute of "([^\"]*)" to "([^\"]*)" if "([^\"]*)" is "([^\"]*)":$/ do |attribute, item_name, value, expect_att, expect_value| + begin + @domain.items[item_name].attributes[attribute]. + set(value, :if => Hash[[[expect_att, expect_value]]]) + rescue Errors::ClientError => e + @error = e + end +end + +When /^I set the "([^\"]*)" attribute of "([^\"]*)" to "([^\"]*)" unless "([^\"]*)" has a value:$/ do |attribute, item_name, value, expect_att| + begin + @domain.items[item_name].attributes[attribute]. + set(value, :unless => expect_att) + rescue Errors::ClientError => e + @error = e + end +end + +When /^I put the following attributes to "([^"]*)"$/ do |item_name, table| + + replace = {} + add = {} + + table.hashes.each do |hash| + name = hash['name'] + value = hash['value'] + if hash['replace'] == 'yes' + replace[name] ||= [] + replace[name] << value + else + add[name] ||= [] + add[name] << value + end + end + + attributes = @domain.items[item_name].attributes + attributes.put(:add => add, :replace => replace) + +end diff --git a/features/simple_db/high_level/step_definitions/select.rb b/features/simple_db/high_level/step_definitions/select.rb new file mode 100644 index 00000000000..d8c68553ffd --- /dev/null +++ b/features/simple_db/high_level/step_definitions/select.rb @@ -0,0 +1,139 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I select items using the following attribute conditions:$/ do |table| + # This would work: + # @result = @domain.items.where(table.rows_hash).to_a + # but hash ordering makes the testing comparison hard + + items = @domain.items + table.raw.each do |(name, value)| + condition = {} + condition[name] = value + items = items.where(condition) + end + @result = items.to_a +end + +When /^a select should have been performed like:$/ do |table| + select_requests = @http_handler.requests_made.select do |req| + action = req.params.find { |p| p.name == "Action" } and + action.value == "Select" + end + + select_requests.map do |req| + if expr = req.params.find { |p| p.name == "SelectExpression" } + expr.value + end + end.compact.should match_select_expression(table) +end + +When /^I select items using the following conditions and substitutions:$/ do |table| + items = @domain.items + table.hashes.each do |condition| + items = items.where(condition["CONDITION"], eval(condition["SUBSTITUTION"])) + end + @result = items.to_a +end + +When /^I select items where "([^\"]*)" is between "([^\"]*)" and "([^\"]*)"$/ do |att, low, high| + condition = {} + condition[att] = low..high + @result = @domain.items.where(condition).to_a +end + +When /^I select items using the following named substitutions:$/ do |table| + condition = table.rows_hash["CONDITION"] + substitutions = Hash[table.rows_hash.reject { |(k,v)| k == "CONDITION" }] + @result = @domain.items.where(condition, substitutions).to_a +end + +When /^I select items ordering by "([^\"]*)" (descending|ascending)$/ do |column, dir| + @result = @domain.items.order(column, dir == "descending" ? :desc : :asc).to_a +end + +When /^I select all items with a limit of (\d+)$/ do |limit| + @result = @domain.items.limit(1).to_a +end + +When /^I select all attributes$/ do + @result = [] + @domain.items.select(:consistent_read => true) { |data| @result << data } +end + +Then /^the result should contain the following attribute data:$/ do |table| + expected = table.hashes.inject({}) do |m, hash| + item_data = (m[hash["item"]] ||= {}) + values = (item_data[hash["attribute"]] ||= []) + values << hash["value"] + m + end + @result.map { |data| data.name }.sort.should == expected.keys.sort + @result.each do |data| + data.attributes.should == expected[data.name] + end +end + +RSpec::Matchers.define :match_select_expression do |table| + + match do |expressions| + matched = expressions.select do |expr| + table.hashes.all? do |part| + value = Regexp.escape(part["VALUE"]) + case part["PART"] + when "condition" + expr =~ /WHERE #{value}\s*(ORDER.*?)?(LIMIT.*)?$/ + when "output_list" + expr =~ /^SELECT #{value} FROM/ + when "sort_instructions" + expr =~ /ORDER BY #{value}\s*(LIMIT.*)?$/ + when "limit" + expr =~ /LIMIT #{value}$/ + end + end + end + matched.length > 0 + end + + failure_message_for_should do |expressions| + "expected the description above to match any one of these expressions: #{expressions.inspect}" + end + + failure_message_for_should_not do |expressions| + "the table above matched the following expression(s):#{expressions.join}" + end + +end + +Given /^I have (\d+) items in a domain$/ do |count| + ('0'..((count.to_i-1).to_s)).each do |n| + @domain.items[n].attributes.add('foo' => 'bar') + end +end + +When /^I enumerate items with a limit of (\d+) and batch size of (\d+)$/ do |limit, batch_size| + @domain.items.each( + :limit => limit, + :batch_size => batch_size, + :consistent_read => true + ) {|i|} +end + +When /^I select item data with a limit of (\d+) and batch size of (\d+)$/ do |limit, batch_size| + @domain.items.select(:all, + :limit => limit, + :batch_size => batch_size, + :consistent_read => true + ) {|i|} +end + diff --git a/features/simple_db/low_level/domains.feature b/features/simple_db/low_level/domains.feature new file mode 100644 index 00000000000..469242ec2dd --- /dev/null +++ b/features/simple_db/low_level/domains.feature @@ -0,0 +1,68 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@simple_db @domains @low_level +Feature: SimpleDB Low-Level Domains + + As a user of SimpleDB + I want a low-level interface to create, list and describe domains + + @create_domain + Scenario: create a domain + When I call create_domain with "ruby_integration_test" + Then I should receive a simple db response + + @create_domain @endpoint + Scenario: create a domain in a different region + When I call create_domain with "ruby_integration_test" in "sdb.us-west-1.amazonaws.com" + Then I should receive a simple db response + Then a request should have been made like: + | TYPE | NAME | VALUE | + | http | host | sdb.us-west-1.amazonaws.com | + | param | Action | CreateDomain | + | param | DomainName | ruby_integration_test | + + @delete_domain + Scenario: delete a domain + When I call delete_domain with "ruby_integration_test" + Then I should receive a simple db response + + @list_domains + Scenario: list domains with limit + Given I call create_domain 3 times + When I call list_domains with a limit of 2 + Then I should receive a simple db response + And The response should have a next token + And The response should have 2 domains + + @list_domains @paginate + Scenario: list domains with limit and next token + Given I call create_domain 3 times + And I call list_domains with a limit of 2 + When I call list_domains with a limit of 2 and a next_token + Then I should receive a simple db response + And The response should have at least 1 different domains + + @domain_metadata + Scenario: domain_metadata + Given I call delete_domain with "sample_domain" + And I call create_domain with "sample_domain" + When I call domain_metadata with "sample_domain" + Then I should receive a simple db response + And The response should have metadata with "item_count" of 0 + And The response should have metadata with "item_names_size_bytes" of 0 + And The response should have metadata with "attribute_name_count" of 0 + And The response should have metadata with "attribute_names_size_bytes" of 0 + And The response should have metadata with "attribute_value_count" of 0 + And The response should have metadata with a timestamp diff --git a/features/simple_db/low_level/items.feature b/features/simple_db/low_level/items.feature new file mode 100644 index 00000000000..1f4597a76a5 --- /dev/null +++ b/features/simple_db/low_level/items.feature @@ -0,0 +1,63 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@simple_db @items @low_level +Feature: SimpleDB Domains + + As a user of SimpleDB + I want to query, read, and modify items + So that I can use SimpleDB + + @put_attributes + Scenario: Put attributes + When I replace the values of attribute "color" with "blue" on item "item001" + Then The "color" attribute of item "item001" should eventually be "blue" + + @put_attributes + Scenario: Put attributes (with existing value) + Given I have an item named "item001" whose "color" attribute is "blue" + When I replace the values of attribute "color" with "red" on item "item001" + Then The "color" attribute of item "item001" should eventually be "red" + + @put_attributes + Scenario: Put attributes + When I replace the values of attribute "color\0" with "blue\0" on item "item001" + Then The "color\0" attribute of item "item001" should eventually be "blue\0" + + @select + Scenario: Select + Given I have an item named "item001" whose "color" attribute is "blue" + When I perform the select expression "select * from `{domain_name}` where color = 'blue'" + Then the response should include an item named "item001" with: + | attribute_name | value | + | color | blue | + + @select @paginate + Scenario: Select with pagination + Given I have 2 items with 1024 bytes of attribute data each + When I perform the select expression "SELECT * FROM `{domain_name}` LIMIT 1" + Then the response should include a next token + And making the same request with the next token should yield another page of results + + @delete_attributes + Scenario: Delete attributes + Given I have an item named "item001" whose "color" attribute is "blue" + When I delete the "blue" value of the "color" attribute of item "item001" + Then the item "item001" should eventually not have a "color" attribute + + @batch_delete_attributes + Scenario: Delete a batch of attributes + Given I have 25 items with 3 single-valued attributes each + When I perform a batch delete of all the attributes + Then the attributes should no longer exist diff --git a/features/simple_db/low_level/step_definitions/domains.rb b/features/simple_db/low_level/step_definitions/domains.rb new file mode 100644 index 00000000000..375ba5b10d1 --- /dev/null +++ b/features/simple_db/low_level/step_definitions/domains.rb @@ -0,0 +1,81 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Given /^I call create_domain (\d+) times$/ do |count| + count.to_i.times do |n| + create_domain_low_level(:domain_name => "test-domain-#{n}") + end +end + +When /^I call create_domain with "([^"]*)"$/ do |domain_name| + create_domain_low_level(:domain_name => domain_name) +end + +When /^I call create_domain with "([^"]*)" in "([^"]*)"$/ do |domain_name, endpoint| + create_domain_low_level(:domain_name => domain_name, :endpoint => endpoint) +end + +When /^I call list_domains with a limit of (\d+)$/ do |limit| + @response = @sdb_client.list_domains(:limit => limit.to_i) +end + +When /^I call list_domains with a limit of (\d+) and a next_token$/ do |limit| + @prev_response = @response + @response = @sdb_client.list_domains( + :limit => limit.to_i, + :next_token => @response.next_token + ) +end + +When /^I call delete_domain with "([^"]*)"$/ do |domain_name| + @response = @sdb_client.delete_domain(:domain_name => domain_name) +end + +When /^I call domain_metadata with "([^"]*)"$/ do |domain_name| + @response = @sdb_client.domain_metadata(:domain_name => domain_name) +end + +Then /^The response should have at least (\d+) different domains$/ do |min| + @response.domain_names.length.should >= min.to_i + @response.domain_names.should_not == @prev_response.domain_names +end + + +Then /^I should receive a simple db response$/ do + @response.should be_a(SimpleDB::Response) + @response.response_metadata.request_id.should be_a(String) + @response.response_metadata.request_id.should_not == '' + @response.response_metadata.box_usage.should be_a(Float) +end + +Then /^The response should have (\d+) domains$/ do |count| + @response.domain_names.length.should == count.to_i + @response.domain_names.each do |domain_name| + domain_name.should be_a(String) + domain_name.should_not == '' + end +end + +Then /^The response should have a next token$/ do + @response.next_token.should be_a(String) + @response.next_token.should_not == '' +end + +Then /^The response should have metadata with "([^\"]*)" of (\d+)$/ do |method, size| + @response.send(method).should == size.to_i +end + +Then /^The response should have metadata with a timestamp$/ do + @response.timestamp.should_not be_nil + @response.timestamp.to_s.should =~ /\d+/ +end diff --git a/features/simple_db/low_level/step_definitions/items.rb b/features/simple_db/low_level/step_definitions/items.rb new file mode 100644 index 00000000000..8988bcf6504 --- /dev/null +++ b/features/simple_db/low_level/step_definitions/items.rb @@ -0,0 +1,130 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +include AWS + +Before("@simple_db", "@low_level", "@items") do + domain_name = "ruby-integration-test-#{Time.now.to_i}" + create_domain_low_level(:domain_name => domain_name) +end + +When /^I replace the values of attribute "([^\"]*)" with "([^\"]*)" on item "([^\"]*)"$/ do |attribute, value, item| + attribute = eval("\"#{attribute}\"") + value = eval("\"#{value}\"") + @sdb_client.put_attributes(:domain_name => @domain_name, + :item_name => item, + :attributes => [{ :name => attribute, + :value => value, + :replace => true }]) +end + +Then /^The "([^\"]*)" attribute of item "([^\"]*)" should eventually be "([^\"]*)"$/ do |attribute, item, value| + attribute = eval("\"#{attribute}\"") + value = eval("\"#{value}\"") + @result = + @sdb_client.get_attributes(:domain_name => @domain_name, + :item_name => item, + :consistent_read => true, + :attribute_names => [attribute]) + @result.attributes.size.should == 1 + @result.attributes.first.name.should == attribute + @result.attributes.first.value.should == value +end + +Given /^I have an item named "([^\"]*)" whose "([^\"]*)" attribute is "([^\"]*)"$/ do |item, attr, value| + @sdb_client.put_attributes(:domain_name => @domain_name, + :item_name => item, + :attributes => [{ :name => attr, + :value => value }]) +end + +When /^I perform the select expression "([^\"]*)"$/ do |expr| + expr.sub!("{domain_name}", @domain_name) + @result = @sdb_client.select(:select_expression => expr, + :consistent_read => true) +end + +Then /^the response should include an item named "([^\"]*)" with:$/ do |item, table| + item = @result.items.find { |i| i.name == item } + item.should_not be_nil + table.rows.each do |(name, value)| + att = item.attributes.find { |att| att.name == name } + att.should_not be_nil + att.value.should == value + end +end + +Given /^I have (\d+) items with (\d+) bytes of attribute data each$/ do |n, bytes| + attribute_count = bytes.to_i / 1024 + attributes = (1..attribute_count).map do |i| + { :name => "att#{i}", + :value => (1..1024).inject("") { |m,i| m << (i % 36).to_s(36) } } + end + (1..(n.to_i)).each_slice(25) do |items| + @sdb_client.batch_put_attributes(:domain_name => @domain_name, + :items => items.map do |item| + { :name => "item#{item}", + :attributes => attributes } + end) + end +end + +Then /^the response should include a next token$/ do + @result.next_token.should be_a(String) +end + +Then /^making the same request with the next token should yield another page of results$/ do + @result = @sdb_client.select(@result.request_options.merge(:next_token => @result.next_token)) + @result.items.should_not be_empty +end + +When /^I delete the "([^\"]*)" value of the "([^\"]*)" attribute of item "([^\"]*)"$/ do |value, attribute, item| + @sdb_client.delete_attributes(:domain_name => @domain_name, + :item_name => item, + :attributes => [{ :name => attribute, + :value => value }]) +end + +Then /^the item "([^\"]*)" should eventually not have a "([^\"]*)" attribute$/ do |item, attribute| + result = @sdb_client.get_attributes(:domain_name => @domain_name, + :item_name => item, + :consistent_read => true) + result.attributes.map(&:name).should_not include(attribute) +end + +Given /^I have (\d+) items with (\d+) single\-valued attributes each$/ do |item_count, attribute_count| + @items = (1..(item_count.to_i)).map do |i| + { :name => "item#{i}", + :attributes => (1..(attribute_count.to_i)).map do |d| + { :name => "attribute#{d}", + :value => d.to_s } + end } + end + @sdb_client.batch_put_attributes(:domain_name => @domain_name, + :items => @items) + sleep 0.5 +end + +When /^I perform a batch delete of all the attributes$/ do + @sdb_client.batch_delete_attributes(:domain_name => @domain_name, + :items => @items) +end + +Then /^the attributes should no longer exist$/ do + @items.each do |item| + result = @sdb_client.get_attributes(:domain_name => @domain_name, + :item_name => item[:name], + :consistent_read => true) + result.attributes.should be_empty + end +end diff --git a/features/simple_email_service/email_addresses.feature b/features/simple_email_service/email_addresses.feature new file mode 100644 index 00000000000..ccbee9b5cc6 --- /dev/null +++ b/features/simple_email_service/email_addresses.feature @@ -0,0 +1,38 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ses @email_addresses +Feature: Managing SES email addresses + + I want to be able to create, list and delete verified email addresses. + + Scenario: Verify a new email address + When I ask to verify the email address "foo@bar.com" + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | VerifyEmailAddress | + | param | EmailAddress | foo@bar.com | + + Scenario: Delete a verified email address + When I ask to delete the email address "foo@bar.com" + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DeleteVerifiedEmailAddress | + | param | EmailAddress | foo@bar.com | + + Scenario: List verified email addresses + When I enumerate verified email addresses + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | ListVerifiedEmailAddresses | diff --git a/features/simple_email_service/errors.feature b/features/simple_email_service/errors.feature new file mode 100644 index 00000000000..5d407c12797 --- /dev/null +++ b/features/simple_email_service/errors.feature @@ -0,0 +1,27 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ses @errors +Feature: SES modeled errors + + I want to be able to rescue named errors. + + Scenario Outline: Rescue SES InvalidParameterValue + When I ask to verify the email address "abc123" rescuing "" + Then I should rescue the error with code "" + + Examples: + | class | code | + | SimpleEmailService::Errors::InvalidParameterValue | InvalidParameterValue | + | Errors::ClientError | InvalidParameterValue | diff --git a/features/simple_email_service/quotas.feature b/features/simple_email_service/quotas.feature new file mode 100644 index 00000000000..b9a7d59223e --- /dev/null +++ b/features/simple_email_service/quotas.feature @@ -0,0 +1,27 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ses @quotas +Feature: SES Quotas + + Scenario: Getting SES quotas + When I get quotas + Then I should get a hash with the following keys and value types: + | KEY | TYPE | + | max_24_hour_send | Integer | + | max_send_rate | Float | + | sent_last_24_hours | Integer | + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | GetSendQuota | diff --git a/features/simple_email_service/send.feature b/features/simple_email_service/send.feature new file mode 100644 index 00000000000..21b9209da27 --- /dev/null +++ b/features/simple_email_service/send.feature @@ -0,0 +1,57 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ses @send +Feature: Sending email with SES + + I want to be able to send email using Amazon Simple Email Service + + Scenario: Send an email + When I send email to a verified email address + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | SendEmail | + | param | Message.Subject.Data | A Sample Email | + | param | Message.Body.Text.Data | sample text | + | param_match | Destination.ToAddresses.member.1 | noreply@example.com | + | param_match | Source | noreply@example.com | + + Scenario: Send a raw email + When I send a raw email like: + """ + Subject: A Sample Email + From: noreply@example.com + To: noreply@example.com + Content-Type: text/plain; charset="utf-8" + Content-Transfer-Encoding: base64 + MIME-Version: 1.0 + + c2FtcGxlIHRleHQNCg== + """ + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | SendRawEmail | + + Scenario: Send a raw email + When I send a raw email to "foo@bar.com" from "bar@foo.com" like: + """ + Subject: A Sample Email + + test email message + """ + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | SendRawEmail | + | param_match | Source | bar@foo.com | + | param_match | Destinations.member.1 | foo@bar.com | diff --git a/features/simple_email_service/statistics.feature b/features/simple_email_service/statistics.feature new file mode 100644 index 00000000000..8a260ea7cd4 --- /dev/null +++ b/features/simple_email_service/statistics.feature @@ -0,0 +1,28 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@ses @statistics +Feature: Getting SES Send Statistics + + I want to be able to get statistics on the email I've sent using SES. + + Scenario: Get statistics + When I ask for the statistics + Then the result should be an array + And each member should have the following keys: + | delivery_attempts | + | rejects | + | complaints | + | bounces | + And each member should have a sent timestamp diff --git a/features/simple_email_service/step_definitions/email_addresses.rb b/features/simple_email_service/step_definitions/email_addresses.rb new file mode 100644 index 00000000000..95adf9c6fb3 --- /dev/null +++ b/features/simple_email_service/step_definitions/email_addresses.rb @@ -0,0 +1,24 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I ask to verify the email address "([^"]*)"$/ do |email_address| + @ses.email_addresses.verify(email_address) +end + +When /^I ask to delete the email address "([^"]*)"$/ do |email_address| + @ses.email_addresses.delete(email_address) +end + +When /^I enumerate verified email addresses$/ do + @ses.email_addresses.to_a +end diff --git a/features/simple_email_service/step_definitions/quotas.rb b/features/simple_email_service/step_definitions/quotas.rb new file mode 100644 index 00000000000..dd525de2477 --- /dev/null +++ b/features/simple_email_service/step_definitions/quotas.rb @@ -0,0 +1,25 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I get quotas$/ do + @quotas = @ses.quotas +end + +Then /^I should get a hash with the following keys and value types:$/ do |table| + table.hashes.each do |row| + key = row['KEY'].to_sym + type = Object.const_get(row['TYPE']) + @quotas.keys.should include(key) + @quotas[key].should be_a(type) + end +end diff --git a/features/simple_email_service/step_definitions/send.rb b/features/simple_email_service/step_definitions/send.rb new file mode 100644 index 00000000000..7b5212646fe --- /dev/null +++ b/features/simple_email_service/step_definitions/send.rb @@ -0,0 +1,42 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I send email to a verified email address$/ do + + begin + @ses.send_email( + :subject => 'A Sample Email', + :from => 'noreply@example.com', + :to => 'noreply@example.com', + :body_text => 'sample text') + rescue => e + e.message.should match(/Email address is not verified/) + end + +end + +When /^I send a raw email like:$/ do |raw_email| + begin + @ses.send_raw_email(raw_email) + rescue => e + e.message.should match(/Email address is not verified/) + end +end + +When /^I send a raw email to "([^"]*)" from "([^"]*)" like:$/ do |to,from,raw| + begin + @ses.send_raw_email(raw, :to => to, :from => from) + rescue => e + e.message.should match(/Email address is not verified/) + end +end diff --git a/features/simple_email_service/step_definitions/ses.rb b/features/simple_email_service/step_definitions/ses.rb new file mode 100644 index 00000000000..df7f26cd54c --- /dev/null +++ b/features/simple_email_service/step_definitions/ses.rb @@ -0,0 +1,25 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Before("@ses") do + + @ses = SimpleEmailService.new + @ses_client = @ses.client + +end + +After("@ses") do + + # no cleanup yet + +end diff --git a/features/simple_email_service/step_definitions/statistics.rb b/features/simple_email_service/step_definitions/statistics.rb new file mode 100644 index 00000000000..507097a7285 --- /dev/null +++ b/features/simple_email_service/step_definitions/statistics.rb @@ -0,0 +1,29 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I ask for the statistics$/ do + @stats = @result = @ses.statistics +end + +Then /^each member should have the following keys:$/ do |table| + table.raw.flatten.each do |member| + @stats.all? do |stats| + stats.should have_key(member.to_sym) + stats[member.to_sym].should be_an(Integer) + end + end +end + +Then /^each member should have a sent timestamp$/ do + @stats.all? {|stat| stat[:sent].should be_a(Time) } +end diff --git a/features/sns/errors.feature b/features/sns/errors.feature new file mode 100644 index 00000000000..3f29ca95e37 --- /dev/null +++ b/features/sns/errors.feature @@ -0,0 +1,30 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@sns @errors +Feature: Modeled exceptions + + As a user of the SNS high-level interface + I want to get modeled exceptions for client and server errors + So that I can handle those exceptions without doing string comparisons + + Scenario Outline: Rescue InvalidInstanceID.NotFound + Given I create an SNS topic + When I confirm a subscription with token "FOO" and ask that unsubscribe requests be authenticated rescuing "" + Then I should rescue the error with code "" + + Examples: + | class | code | + | SNS::Errors::InvalidParameter | InvalidParameter | + | Errors::ClientError | InvalidParameter | diff --git a/features/sns/step_definitions/sns.rb b/features/sns/step_definitions/sns.rb new file mode 100644 index 00000000000..bb6e4bdac92 --- /dev/null +++ b/features/sns/step_definitions/sns.rb @@ -0,0 +1,29 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Before("@sns") do + + @sns = SNS.new + @sns_client = @sns.client + + @sns_topics_created = [] + +end + +After("@sns") do + + @sns_topics_created.each do |topic| + topic.delete + end + +end diff --git a/features/sns/step_definitions/subscriptions.rb b/features/sns/step_definitions/subscriptions.rb new file mode 100644 index 00000000000..719b07ebfff --- /dev/null +++ b/features/sns/step_definitions/subscriptions.rb @@ -0,0 +1,58 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I subscribe the queue to the topic$/ do + @result = @subscription = @topic.subscribe(@queue) +end + +Then /^the result should be a subscription$/ do + @result.should be_an(SNS::Subscription) +end + +Then /^the subscription should( not)? be in the list of subscriptions for the topic$/ do |should_not| + @topic.subscriptions.send(should_not ? :should_not : :should, include(@subscription)) +end + +Given /^I subscribe the following HTTP endpoints to the topic:$/ do |table| + @subscriptions = [] + table.raw.flatten.each do |endpoint| + @subscriptions << endpoint + @topic.subscribe(endpoint) + end +end + +When /^I ask for the list of subscriptions in my account$/ do + @result = @sns.subscriptions +end + +Then /^the result should contain the subscriptions I created$/ do + @subscriptions.each do |endpoint| + @result.map(&:endpoint).should include(endpoint) + end +end + +When /^I unsubscribe the queue from the topic$/ do + @subscription.unsubscribe +end + +When /^I confirm a subscription with token "([^\"]*)" and ask that unsubscribe requests be authenticated$/ do |token| + @topic.confirm_subscription(token, :authenticate_on_unsubscribe => true) +end + +Given /^I ask for the SNS subscription with ARN "([^\"]*)"$/ do |arn| + @subscription = @result = @sns.subscriptions[arn] +end + +When /^I ask if the subscription exists$/ do + @result = @subscription.exists? +end diff --git a/features/sns/step_definitions/topics.rb b/features/sns/step_definitions/topics.rb new file mode 100644 index 00000000000..4c13641ce27 --- /dev/null +++ b/features/sns/step_definitions/topics.rb @@ -0,0 +1,76 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I create an SNS topic$/ do + @topic_name = "ruby-integration-test-#{Time.now.to_i}" + @topic = @sns.topics.create(@topic_name) + @sns_topics_created << @topic +end + +Then /^the topic should have an ARN$/ do + @topic.arn.should match(/^arn:aws:sns:/) +end + +Then /^the topic should have the correct display name$/ do + @topic.name.should == @topic_name +end + +When /^I delete the topic$/ do + @topic.delete +end + +Then /^it should appear in the topic list$/ do + @sns.topics.should include(@topic) +end + +Then /^it should not appear in the topic list$/ do + @sns.topics.should_not include(@topic) +end + +When /^I set the topic display name to "([^"]*)"$/ do |display_name| + @topic.display_name = display_name +end + +Then /^the topic \#to_h should look like:$/ do |table| + hash = @topic.to_h + table.hashes.each do |h| + hash[h['KEY'].to_sym].should == h['VALUE'] + end +end + +When /^I set the topic policy$/ do + @policy = SNS::Policy.from_json("{\"Version\":\"2008-10-17\",\"Id\":\"__default_policy_ID\",\"Statement\":[{\"Action\":[\"SNS:Publish\",\"SNS:RemovePermission\",\"SNS:SetTopicAttributes\",\"SNS:DeleteTopic\",\"SNS:ListSubscriptionsByTopic\",\"SNS:GetTopicAttributes\",\"SNS:Receive\",\"SNS:AddPermission\",\"SNS:Subscribe\"],\"Sid\":\"__default_statement_ID\",\"Resource\":[\"#{@topic.arn}\"],\"Principal\":{\"AWS\":[\"*\"]},\"Condition\":{\"StringEquals\":{\"AWS:SourceOwner\":\"#{@topic.owner}\"}},\"Effect\":\"Allow\"}]}") + @topic.policy = @policy +end + +Then /^the topic policy should match$/ do + @topic.policy.should == @policy +end + +Given /^I subscribe to the topic with the queue$/ do + @topic.subscribe(@queue) +end + +When /^I publish a message to the topic$/ do + @message = "published message" + @topic.publish(@message) +end + +Then /^The queue should eventually have the message$/ do + received = false + @queue.poll(:initial_timeout => false, :idle_timeout => 1) do |msg| + msg.as_sns_message.body.should == @message + received = true + end + received.should == true +end diff --git a/features/sns/subscriptions.feature b/features/sns/subscriptions.feature new file mode 100644 index 00000000000..cbcc3ea1d9b --- /dev/null +++ b/features/sns/subscriptions.feature @@ -0,0 +1,62 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@sns @subscriptions @sqs +Feature: SNS subscriptions + + I want to be able to create, confirm, delete, and list subscriptions. + + Scenario: Subscribe an SQS queue to an SNS topic + Given I create a queue + And I create an SNS topic + When I subscribe the queue to the topic + Then the result should be a subscription + And the subscription should be in the list of subscriptions for the topic + + Scenario: List SNS subscriptions by topic + Given I create an SNS topic + And I subscribe the following HTTP endpoints to the topic: + | http://aws.amazon.com/sns-test-one/ | + | http://aws.amazon.com/sns-test-two/ | + When I ask for the list of subscriptions in my account + Then the result should contain the subscriptions I created + + Scenario: Unsubscribe from an SNS topic + Given I create a queue + And I create an SNS topic + And I subscribe the queue to the topic + When I unsubscribe the queue from the topic + Then the subscription should not be in the list of subscriptions for the topic + + # FIXME: write a test that can successfully confirm an actual subscription + Scenario: Confirm subscription + Given I create an SNS topic + When I confirm a subscription with token "FOO" and ask that unsubscribe requests be authenticated, ignoring errors + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | ConfirmSubscription | + | param | Token | FOO | + | param | AuthenticateOnUnsubscribe | true | + + Scenario: Ask if an SNS subscription exists (does not exist) + Given I ask for the SNS subscription with ARN "foo" + When I ask if the subscription exists + Then the result should be false + + Scenario: Ask if an SNS subscription exists (exists) + Given I create a queue + And I create an SNS topic + And I subscribe the queue to the topic + When I ask if the subscription exists + Then the result should be true diff --git a/features/sns/topics.feature b/features/sns/topics.feature new file mode 100644 index 00000000000..3d8d9087665 --- /dev/null +++ b/features/sns/topics.feature @@ -0,0 +1,68 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@sns @topics +Feature: Managing SNS Topics + + I want to be able to create, list and delete SNS topics. + + Scenario: Create a new topic + When I create an SNS topic + Then the topic should have an ARN + And the topic should have the correct display name + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | CreateTopic | + + Scenario: Create a new topic and find it + When I create an SNS topic + Then it should appear in the topic list + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | ListTopics | + + Scenario: Delete a topic + Given I create an SNS topic + When I delete the topic + Then it should not appear in the topic list + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DeleteTopic | + + Scenario: Setting a topic display name + Given I create an SNS topic + When I set the topic display name to "My Topic" + Then the topic #to_h should look like: + | KEY | VALUE | + | display_name | My Topic | + + Scenario: Set a queue policy + Given I create an SNS topic + When I set the topic policy + Then the topic policy should match + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | SetTopicAttributes | + | param | AttributeName | Policy | + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | GetTopicAttributes | + + @sqs + Scenario: Publish a message to a topic + Given I create an SNS topic + And I create a queue + And I subscribe to the topic with the queue + When I publish a message to the topic + Then The queue should eventually have the message diff --git a/features/sqs/messages.feature b/features/sqs/messages.feature new file mode 100644 index 00000000000..910cdccb88b --- /dev/null +++ b/features/sqs/messages.feature @@ -0,0 +1,101 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@sqs @messages +Feature: SQS Messages + + I want to be able to send and process messages. + + Scenario: Send an SQS message + Given I create a queue + When I send the message "HELLO" + Then the result should include a message ID + And the result should have an MD5 digest of "eb61eead90e3b899c6bcbe27ac581660" + And I should eventually be able to receive "HELLO" from the queue + + Scenario: Visibility timeout + Given I create a queue + And I send the message "HELLO" + When I receive a message with a 60-second visibility timeout + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | ReceiveMessage | + | param | VisibilityTimeout | 60 | + + Scenario: Delete an SQS message + Given I create a queue + And I send the message "HELLO" + And I receive the message "HELLO" + When I delete the message + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DeleteMessage | + | param_match | ReceiptHandle | .+ | + + Scenario: Automatic deletion of SQS messages + Given I create a queue + And I send the message "HELLO" + When I receive a message in a block + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | ReceiveMessage | + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | DeleteMessage | + | param_match | ReceiptHandle | .+ | + + @slow + Scenario: Poll for SQS messages + Given I create a queue + And I fork a process to send 50 random numbers to the queue over a period of 30 seconds + When I poll for messages with an idle timeout of 10 seconds + Then I should have received all of the messages within 60 seconds + And each message receipt handle should have been deleted + And the forked process should have completed without errors + + Scenario: Change message visibility timeout + Given I create a queue + And I send the message "HELLO" + And I receive the message "HELLO" + When I set the visibility timeout to 60 seconds + Then a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | ChangeMessageVisibility | + | param_match | ReceiptHandle | .+ | + | param | VisibilityTimeout | 60 | + + Scenario: Receive SQS message with all attributes + Given I create a queue + And I send the message "HELLO" + When I receive the message "HELLO" requesting the following attributes: + | all | + Then the message should have the following time fields: + | sent_at | + | first_received_at | + And the message should have an integer receive count + And the message should have a string sender ID + + Scenario: Receive SQS message with explicit attributes + Given I create a queue + And I send the message "HELLO" + When I receive the message "HELLO" requesting the following attributes: + | sender_id | + | sent_at | + | first_received_at | + | receive_count | + Then the message should have the following time fields: + | sent_at | + | first_received_at | + And the message should have an integer receive count + And the message should have a string sender ID diff --git a/features/sqs/policies.feature b/features/sqs/policies.feature new file mode 100644 index 00000000000..cb930646e31 --- /dev/null +++ b/features/sqs/policies.feature @@ -0,0 +1,28 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@sqs @queues +Feature: SQS Queues + + I want to manage policies on SQS queues. + + Scenario: Set a queue policy + Given I create a queue + When I set the queue policy + Then the queue policy should match + And a request should have been made like: + | TYPE | NAME | VALUE | + | param | Action | GetQueueAttributes | + | param | AttributeName.1 | Policy | + | param | AttributeName.2 | QueueArn | diff --git a/features/sqs/queues.feature b/features/sqs/queues.feature new file mode 100644 index 00000000000..1294c74eaea --- /dev/null +++ b/features/sqs/queues.feature @@ -0,0 +1,79 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@sqs @queues +Feature: SQS Queues + + I want to be able to create, list, and delete queues + + Scenario: Create SQS Queue + When I create a queue + Then the result should be a queue object + And the queue should be in the list of all queues + + Scenario Outline: List SQS Queues + Given I create a queue with a name that starts with "" + When I ask for the list of queues starting with "" + Then the result include the queue I created + + Examples: + | create prefix | list prefix | expectation | + | fred | fred | should | + | freddy | fred | should | + | bob | fred | should not | + + Scenario: Ask if SQS queue exists (does not exist) + Given I ask for the queue with URL "http://sqs.us-east-1.amazonaws.com/123456781234/does-not-exist" + When I ask if the queue exists + Then the result should be false + + Scenario: Delete Queue + Given I create a queue + When I delete the queue + Then the queue should eventually not exist + + Scenario: Ask if SQS queue exists (exists) + Given I create a queue + And I wait for it to be in the list of all queues + When I ask if the queue exists + Then the result should be true + + Scenario: Get queue attributes + Given I create a queue + When I access the queue attributes + Then the following integer fields should be present: + | visible_messages | + | invisible_messages | + | visibility_timeout | + | maximum_message_size | + | message_retention_period | + And the following date/time fields should contain values within the last hour: + | created_timestamp | + | last_modified_timestamp | + And the queue ARN should end with the queue name + + Scenario: Set SQS message visibility timeout + Given I create a queue + When I set the queue's visibility timeout to 42 + Then the queue's visibility timeout should eventually be 42 + + Scenario: Set SQS max. message size + Given I create a queue + When I set the queue's maximum message size to 1024 + Then the queue's maximum message size should eventually be 1024 + + Scenario: Set SQS message retention period + Given I create a queue + When I set the queue's message retention period to 3600 + Then the queue's message retention period should eventually be 3600 diff --git a/features/sqs/step_definitions/messages.rb b/features/sqs/step_definitions/messages.rb new file mode 100644 index 00000000000..6b6d85ccbde --- /dev/null +++ b/features/sqs/step_definitions/messages.rb @@ -0,0 +1,145 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I send the message "([^\"]*)"$/ do |msg| + @result = @queue.send_message(msg) +end + +Then /^the result should include a message ID$/ do + @result.message_id.should be_a(String) +end + +Then /^the result should have an MD5 digest of "([^\"]*)"$/ do |digest| + @result.md5.should == digest +end + +Then /^I should eventually be able to receive "([^\"]*)" from the queue$/ do |msg| + eventually(10) do + if msg = @queue.receive_message + msg.body.should == msg + end + end +end + +When /^I receive a message with a (\d+)\-second visibility timeout$/ do |timeout| + @queue.receive_message(:visibility_timeout => timeout.to_i) +end + +Given /^I receive the message "([^\"]*)"$/ do |msg| + eventually(10) do + @message = @queue.receive_message + @message.body.should == msg + end +end + +When /^I delete the message$/ do + @message.delete +end + +When /^I receive a message in a block$/ do + eventually(10) do + @queue.receive_message { |msg| @message = msg } + @message.should_not be_nil + end +end + +Given /^I fork a process to send (\d+) random numbers to the queue over a period of (\d+) seconds$/ do |count, seconds| + (count, seconds) = [count, seconds].map { |i| i.to_i } + + @numbers = [] + count.times { @numbers << rand(1000000) } + @child_pid = fork do + begin + start_time = Time.now + end_time = start_time + seconds + while !@numbers.empty? + begin + @queue.send_message(@numbers.pop.to_s) + seconds_remaining = end_time - Time.now + end until + @numbers.empty? or + seconds_remaining > 0 && seconds_remaining.to_f / @numbers.size >= 1.0 + sleep 1 + end + rescue Exception => e + exit 1 + else + exit 0 + end + end +end + +When /^I poll for messages with an idle timeout of (\d+) seconds$/ do |idle| + @received = [] + @handles = [] + @start_time = Time.now + + require 'timeout' + Timeout.timeout(120) do + @queue.poll(:idle_timeout => idle.to_i) do |msg| + @received << msg.body.to_i + @handles << msg.handle + end + end + @end_time = Time.now +end + +Then /^I should have received all of the messages within (\d+) seconds$/ do |time| + (@numbers - @received).should be_empty + (@end_time - @start_time).should < time.to_i +end + +Then /^each message receipt handle should have been deleted$/ do + @handles.each do |handle| + @http_handler.requests_made.select do |req| + action = req.params.find { |p| p.name == "Action" } and + action.value == "DeleteMessage" + end.map do |req| + req.params.find { |p| p.name == "ReceiptHandle" } + end.compact.map do |param| + param.value + end.should include(handle) + end +end + +Then /^the forked process should have completed without errors$/ do + Process.wait(@child_pid) + $?.exitstatus.should == 0 +end + +When /^I set the visibility timeout to (\d+) seconds$/ do |timeout| + @message.visibility_timeout = timeout.to_i +end + +When /^I receive the message "([^\"]*)" requesting the following attributes:$/ do |msg, table| + attributes = table.raw.flatten.map { |att| att.to_sym } + eventually(10) do + @message = @queue.receive_message(:attributes => attributes) + @message.body.should == msg + end +end + +Then /^the message should have the following time fields:$/ do |table| + table.raw.flatten.each do |f| + @message.send(f.to_sym).should be_a(Time) + @message.send(f.to_sym).should be_within(60*60).of(Time.now) + end +end + +Then /^the message should have an integer receive count$/ do + @message.receive_count.should be_an(Integer) +end + +Then /^the message should have a string sender ID$/ do + @message.sender_id.should be_a(String) +end diff --git a/features/sqs/step_definitions/policies.rb b/features/sqs/step_definitions/policies.rb new file mode 100644 index 00000000000..e8f9d5bfa3d --- /dev/null +++ b/features/sqs/step_definitions/policies.rb @@ -0,0 +1,25 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I set the queue policy$/ do + @policy = AWS::SQS::Policy.new + @policy.allow( + :actions => :any, + :principles => ["arn:aws:iam::681294939609:root"], + :resources => @queue) + @queue.policy = @policy +end + +Then /^the queue policy should match$/ do + @queue.policy.to_h.should == @policy.to_h +end diff --git a/features/sqs/step_definitions/queues.rb b/features/sqs/step_definitions/queues.rb new file mode 100644 index 00000000000..b97bf502345 --- /dev/null +++ b/features/sqs/step_definitions/queues.rb @@ -0,0 +1,95 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^I create a queue$/ do + @queue_name = "ruby-integration-test-#{Time.now.to_i}" + @result = @queue = @sqs.queues.create(@queue_name) + @created_queues << @queue +end + +Then /^the result should be a queue object$/ do + @result.should be_an(SQS::Queue) +end + +Then /^the queue should be in the list of all queues$/ do + eventually(60) { @sqs.queues.should include(@queue) } +end + +Given /^I create a queue with a name that starts with "([^\"]*)"$/ do |name| + @queue_name = "#{name}-ruby-integration-test-#{Time.now.to_i}" + @result = @queue = @sqs.queues.create(@queue_name) + @created_queues << @queue +end + +When /^I ask for the list of queues starting with "([^\"]*)"$/ do |name| + @result = @sqs.queues.with_prefix(name) +end + +Then /^the result should( not)? include the queue I created$/ do |should_not| + eventually(60) do + @result.send(should_not == " not" ? :should_not : :should, include(@queue)) + end +end + +Given /^I ask for the queue with URL "([^\"]*)"$/ do |url| + @result = @queue = @sqs.queues[url] +end + +When /^I ask if the queue exists$/ do + @result = @queue.exists? +end + +Given /^I wait for it to be in the list of all queues$/ do + Given "the queue should be in the list of all queues" +end + +When /^I create a queue named "([^\"]*)"$/ do |name| + @result = @queue = @sqs.queues.create(name) + @created_queues << @queue +end + +Given /^I delete the queue$/ do + @queue.delete +end + +Then /^the queue should eventually not exist$/ do + eventually(60) { @queue.exists?.should be_false } +end + +When /^I access the queue attributes$/ do + # sugar +end + +Then /^the following integer fields should be present:$/ do |table| + table.raw.flatten.each do |field| + @queue.send(field).should be_an(Integer) + end +end + +Then /^the following date\/time fields should contain values within the last hour:$/ do |table| + table.raw.flatten.each do |field| + @queue.send(field).should be_within(60*60).of(Time.now) + end +end + +Then /^the queue ARN should end with the queue name$/ do + @queue.arn.should =~ /#{Regexp.escape(@queue_name)}$/ +end + +When /^I set the queue\'s (.*) to (\d+)$/ do |field, value| + @queue.send(field.gsub(" ", "_")+"=", value.to_i) +end + +Then /^the queue\'s (.*) should eventually be (\d+)$/ do |field, value| + eventually(60) { @queue.send(field.gsub(" ", "_")).should == value.to_i } +end diff --git a/features/sqs/step_definitions/sqs.rb b/features/sqs/step_definitions/sqs.rb new file mode 100644 index 00000000000..40b814e9cef --- /dev/null +++ b/features/sqs/step_definitions/sqs.rb @@ -0,0 +1,31 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Before("@sqs") do + + @sqs = SQS.new + @sqs_client = @sqs.client + @created_queues = [] + +end + +After("@sqs") do + + @created_queues.each do |queue| + begin + queue.delete + rescue SQS::Errors::NonExistentQueue + end + end + +end diff --git a/features/step_definitions.rb b/features/step_definitions.rb new file mode 100644 index 00000000000..273b36cd704 --- /dev/null +++ b/features/step_definitions.rb @@ -0,0 +1,216 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +When /^(.*), ignoring errors$/ do |step| + When step rescue nil +end + +When /^(.*) again$/ do |step| + When step rescue nil +end + +Given /^I start a memoization block$/ do + @http_handler.requests_made.clear + AWS.start_memoizing +end + +After("@memoized") do + AWS.stop_memoizing +end + +Then /^the client should not have been called$/ do + @http_handler.requests_made.should == [] +end + +Then /^a request should have been made like:$/ do |table| + table.should match_any_requests(@http_handler.requests_made) +end + +Then /^no requests? should have been made like:$/ do |table| + table.should_not match_any_requests(@http_handler.requests_made) +end + +Then /^(?:exactly )?(\d+) requests? should have been made like:$/ do |count, table| + table.should match_requests(@http_handler.requests_made, count.to_i) +end + +Then /^at least (\d+) requests? should have been made like:$/ do |count, table| + table.should match_requests_min(@http_handler.requests_made, count.to_i) +end + +Then /^at most (\d+) requests? should have been made like:$/ do |count, table| + table.should match_requests_max(@http_handler.requests_made, count.to_i) +end + +RSpec::Matchers.define :match_any_requests do |requests| + + match do |table| + matched = requests_matching(requests, table) + matched.length > 0 + end + + failure_message_for_should do |table| + tables = table_formatted_requests(requests) + "expected the table above match any one of these requests:#{tables.join}" + end + + failure_message_for_should_not do |table| + tables = table_formatted_requests(requests_matching(requests, table)) + "the table above matched the following request(s):#{tables.join}" + end + +end + +RSpec::Matchers.define :match_requests do |requests,count| + + match do |table| + matched = requests_matching(requests, table) + matched.length == count + end + + failure_message_for_should do |table| + matched = requests_matching(requests, table) + tables = table_formatted_requests(requests) + "matched #{matched.length}, should have matched #{count}, showing all request(s):#{tables.join}" + end + +end + +RSpec::Matchers.define :match_requests_min do |requests,min| + + match do |table| + matched = requests_matching(requests, table) + matched.length >= min + end + + failure_message_for_should do |table| + matched = requests_matching(requests, table) + unmatched = requests - matched + tables = table_formatted_requests(unmatched) + "matched #{matched.length}, should have matched at least #{min}, showing unmatched request(s):#{tables.join}" + end + +end + +RSpec::Matchers.define :match_requests_max do |requests,max| + + match do |table| + matched = requests_matching(requests, table) + matched.length <= max + end + + failure_message_for_should do |table| + matched = requests_matching(requests, table) + tables = table_formatted_requests(matched) + "matched #{matched.length}, should have matched at most #{max}, showing matched request(s):#{tables.join}" + end + +end + +def requests_matching requests, table + matched = [] + requests.each do |request| + + catch(:non_matching) do + + table.hashes.each do |requirement| + + (type, name, value) = requirement.values_at('TYPE', 'NAME', 'VALUE') + + case type + when 'http' + throw :non_matching unless + case name + when 'verb' then request.http_method == value + when 'host' then request.host == value + when 'host_match' then request.host =~ /^#{value}$/ + when 'path' then request.path == value + when 'path_match' then request.path =~ /^#{value}$/ + when 'uri' then request.uri == value + when 'uri_match' then request.uri =~ /^#{value}$/ + when 'body' then request.body == value + else pending("unhandled http requirement `#{name}`") + end + + when 'param' + throw :non_matching unless begin + param = request.params.detect{|p| p.name == name } + param and param.value.to_s == value + end + + when 'param_match' + throw :non_matching unless begin + param = request.params.detect{|p| p.name == name } + param and param.value.to_s =~ /^#{value}$/ + end + + when 'header' + throw :non_matching unless request.headers[name].to_s == value + + when 'header_match' + throw :non_matching unless request.headers[name].to_s =~ /^#{value}$/ + + else pending("unhandled requirement type `#{type}`") + end + + end + + matched << request + + end + end + matched +end + +def table_formatted_requests requests +# tables = requests.collect do |req| +# table = [] +# table << "|TYPE|NAME|VALUE|" +# table << "|http|verb|#{req.http_method}|" +# table << "|http|host|#{req.host}|" +# table << "|http|path|#{req.path}|" +# table << "|http|uri|#{req.uri}|" +# req.params.each do |param| +# # EEEK! this will likely need to change at some point, we have to remove +# # the newlines because that makes for an invalid table +# table << "|param|#{param.name}|#{param.value.gsub(/\n/, '')}|" +# end +# req.headers.each_pair do |key,value| +# #unless %w(authorization date).include?(key) +# table << "|param|#{key}|#{value}|" +# #end +# end +# Cucumber::Ast::Table.parse(table.join("\n"), nil, nil) +# end +# tables.collect{|t| t.to_s(:color => false, :prefixes => Hash.new('')) } + tables = requests.collect do |req| + table = [] + table << %w(TYPE NAME VALUE) + table << ['http', 'verb', req.http_method.to_s] + table << ['http', 'host', req.host.to_s] + table << ['http', 'path', req.path.to_s] + table << ['http', 'uri', req.uri.to_s] + req.params.each do |param| + table << ['param', param.name.to_s, param.value.to_s] + end + req.headers.each_pair do |key,value| + table << ['header', key.to_s, value.to_s] + end + Cucumber::Ast::Table.new(table) + end + tables.collect{|t| t.to_s(:color => false, :prefixes => Hash.new('')) } +end + +When /^I wait for (\d+) seconds$/ do |seconds| + sleep seconds.to_i +end diff --git a/features/support/common.rb b/features/support/common.rb new file mode 100644 index 00000000000..efa55ca0a2b --- /dev/null +++ b/features/support/common.rb @@ -0,0 +1,197 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +$: << File.join(File.dirname(File.dirname(File.dirname(__FILE__))), "lib") + +require 'aws' + +require 'mocha' +include Mocha::API +require 'net/http' +require 'uri' +require 'yaml' + +# find a config file +dir = Dir.getwd +while dir != "/" and + !File.exists?(File.join(dir, ".ruby_sdk_test_config.yml")) + dir = File.dirname(dir) +end +test_config_file = File.join(dir, ".ruby_sdk_test_config.yml") +test_config_file = File.join(ENV["HOME"], ".ruby_sdk_test_config.yml") unless + File.exists?(test_config_file) +raise "No config file" unless File.exists?(test_config_file) +test_config = YAML.load(File.read(test_config_file)) + +if ENV['SLOW'] == 'true' + puts "setup slow stuff" +end + +at_exit do + if ENV['SLOW'] == 'true' + puts "take down slow stuff" + end +end + +if ENV['LOGGING'] == 'true' + require 'logger' + logger = Logger.new(STDOUT) + logger.formatter = proc do |severity, datetime, progname, msg| + + bold = "\x1b[1m" + color = "\x1b[34m" + reset = "\x1b[0m" + + msg.gsub(/^(.*?\])/, "#{bold}#{color}\\1#{reset}#{bold}") + reset + + end + test_config[:logger] = logger +end + +AfterConfiguration do + AWS.config(test_config) + #require 'amazon/aws/http/event_machine_handler' + #AWS.config(:http_handler => AWS::Http::EventMachineHandler.new) + handler = AWS::Http::Handler.new(AWS.config.http_handler) do |req, resp| + @requests_made ||= [] + @requests_made << req + super(req, resp) + end + class << handler + attr_reader :requests_made + end + AWS.config(:http_handler => handler) +end + +Before do + @buckets_created = [] + @sdb_domains_created = [] + @http_handler = AWS.config.http_handler + @test_config = test_config +end + +After do |scenario| + @buckets_created.each do |bucket_name,endpoint| + begin + begin + list = @s3_client.list_object_versions({ + :bucket_name => bucket_name, + :key_marker => (list ? list.next_key_marker : nil), + :version_id_marker => (list ? list.next_version_id_marker : nil), + :endpoint => endpoint, + }) + list.versions.each do |version| + @s3_client.delete_object({ + :bucket_name => bucket_name, + :key => version.key, + :version_id => version.version_id, + :endpoint => endpoint, + }) + end + end while list.truncated? + rescue + # ignore + end + @s3_client.delete_bucket(:bucket_name => bucket_name, :endpoint => endpoint) rescue nil + end + @http_handler.requests_made.clear if @http_handler.requests_made +end + +## simple db + +Before('@s3') do + @s3 = S3.new + @s3_client = @s3.client +end + +Before('@simple_db') do + @sdb = SimpleDB.new + @sdb_client = @sdb.client +end + +After do |scenario| + @sdb_domains_created.each do |domain_name,endpoint| + @sdb_client.with_options(:sdb_endpoint => endpoint). + delete_domain(:domain_name => domain_name) + end +end + +## log repeated service failures as pending + +Around do |scenario, block| + retries = 0 + begin + block.call + rescue => e + if e.to_s =~ /AccessDenied|OperationAborted|ServiceUnavailable/ and retries == 0 + retries += 1 + retry + else + pending("Service is misbehaving") + end + end +end + +## helpers for creating domains / buckets in a way that they will get cleaned up + +def create_bucket_low_level options = {} + options[:bucket_name] ||= "ruby-integration-test-#{Time.now.to_i}" + @bucket_name = options[:bucket_name] + @endpoint = options[:endpoint] || @s3_client.config.s3_endpoint + @result = @s3_client.create_bucket(options) + @buckets_created << [@bucket_name, @endpoint] + sleep 0.5 # Dumb insurance against eventual consistency +end + +def create_bucket_high_level options = {} + @bucket_name = options.delete(:name) || "ruby-integration-test-#{Time.now.to_i}" + @endpoint = @s3.client.config.s3_endpoint + @bucket = @s3.buckets.create(@bucket_name, options) + @buckets_created << [@bucket_name, @endpoint] +end + +def create_domain_low_level options = {} + @domain_name = options[:domain_name] + @endpoint = options.delete(:endpoint) || @sdb_client.config.simple_db_endpoint + @response = @sdb_client.with_options(:simple_db_endpoint => @endpoint). + create_domain(options) + @sdb_domains_created << [@domain_name, @endpoint] +end + +def create_domain_high_level name = nil + @domain_name = name || "ruby-integration-test-#{Time.now.to_i}" + @endpoint = @sdb.client.config.simple_db_endpoint + @domain = @sdb.domains.create(@domain_name) + @sdb_domains_created << [@domain_name, @endpoint] +end + +def eventually(seconds = 60*5) + sleeps = [1] + while sleeps.inject(0) { |sum, i| sum + i } < seconds + sleeps << sleeps.last * 1.2 + end + request_count = (@http_handler.requests_made || []).size + begin + yield + rescue => e + unless sleeps.empty? or + # no additional requests were made, it's probably a coding error + # in the test + request_count == (@http_handler.requests_made || []).size + sleep(sleeps.shift) + request_count = (@http_handler.requests_made || []).size + retry + end + raise e + end +end diff --git a/lib/aws-sdk.rb b/lib/aws-sdk.rb new file mode 100644 index 00000000000..7d46d24964f --- /dev/null +++ b/lib/aws-sdk.rb @@ -0,0 +1,14 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws' diff --git a/lib/aws.rb b/lib/aws.rb new file mode 100644 index 00000000000..1d683d24348 --- /dev/null +++ b/lib/aws.rb @@ -0,0 +1,63 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# AWS is the root module for all of the Amazon Web Services. It is also +# where you can configure you access to AWS. +# +# = Supported Services +# +# The currently supported services are: +# +# * {EC2} +# * {S3} +# * {SimpleDB} +# * {SimpleEmailService} +# * {SNS} +# * {SQS} +# +# = AWS::Record +# +# In addition to the above services, bundled is an ORM based on AWS services +# See {AWS::Record} for more information. +# +# = Configuration +# +# You call {AWS.config} with a hash of options to configure your +# access to the Amazon Web Services. +# +# At a minimum you need to set your access credentials. See {AWS.config} +# for additional configuration options. +# +# AWS.config( +# :access_key_id => 'ACCESS_KEY_ID', +# :secret_access_key => 'SECRET_ACCESS_KEY') +# +# == Rails +# +# If you are loading AWS inside a Rails web application, place your +# configuration inside: +# +# config/initializers/aws.rb +# +module AWS; end + +require 'aws/common' + +%w(ec2 s3 simple_db simple_email_service sns sqs record rails).each do |service| + $:.each do |load_path| + if (Pathname.new(load_path) + "aws" + "#{service}.rb").exist? + require "aws/#{service}" + break + end + end +end diff --git a/lib/aws/api_config.rb b/lib/aws/api_config.rb new file mode 100644 index 00000000000..aa9e32569ac --- /dev/null +++ b/lib/aws/api_config.rb @@ -0,0 +1,45 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/naming' + +require 'pathname' +require 'yaml' + +module AWS + + # @private + module ApiConfig + + include Naming + + protected + def api_config + + config_file = $:.map do |load_path| + if config_dir = Pathname.new(load_path) + + "aws" + "api_config" and + config_dir.directory? + config_dir.children.select do |child| + child.basename.to_s =~ /^#{service_name}/ + end.sort.last + end + end.compact.sort.last + + YAML.load(config_file.read) + + end + + end + +end diff --git a/README b/lib/aws/api_config/.document similarity index 100% rename from README rename to lib/aws/api_config/.document diff --git a/lib/aws/api_config/EC2-2011-02-28.yml b/lib/aws/api_config/EC2-2011-02-28.yml new file mode 100644 index 00000000000..277e4db8c7e --- /dev/null +++ b/lib/aws/api_config/EC2-2011-02-28.yml @@ -0,0 +1,2314 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +--- +:operations: + AllocateAddress: + :input: + Domain: + - :string + :output: [] + + AssociateAddress: + :input: + InstanceId: + - :string + - :required + PublicIp: + - :string + - :required + AllocationId: + - :string + :output: [] + + AssociateDhcpOptions: + :input: + DhcpOptionsId: + - :string + - :required + VpcId: + - :string + - :required + :output: [] + + AttachVolume: + :input: + VolumeId: + - :string + - :required + InstanceId: + - :string + - :required + Device: + - :string + - :required + :output: + - attachTime: + - :timestamp + - deleteOnTermination: + - :boolean + AttachVpnGateway: + :input: + VpnGatewayId: + - :string + - :required + VpcId: + - :string + - :required + :output: [] + + AuthorizeSecurityGroupIngress: + :input: + GroupName: + - :string + GroupId: + - :string + SourceSecurityGroupName: + - :string + SourceSecurityGroupOwnerId: + - :string + IpProtocol: + - :string + FromPort: + - :integer + ToPort: + - :integer + CidrIp: + - :string + IpPermissions: + - :list: + - :structure: + IpProtocol: + - :string + - :rename: IpProtocol + FromPort: + - :integer + - :rename: FromPort + ToPort: + - :integer + - :rename: ToPort + Groups: + - :list: + - :structure: + UserId: + - :string + - :rename: UserId + GroupName: + - :string + - :rename: GroupName + GroupId: + - :string + - :rename: GroupId + - :rename: UserIdGroupPairs + IpRanges: + - :list: + - :structure: + CidrIp: + - :string + - :rename: CidrIp + - :rename: IpRanges + :output: [] + + BundleInstance: + :input: + InstanceId: + - :string + - :required + Storage: + - :structure: + S3: + - :structure: + Bucket: + - :string + - :rename: Bucket + Prefix: + - :string + - :rename: Prefix + AWSAccessKeyId: + - :string + UploadPolicy: + - :string + - :rename: UploadPolicy + UploadPolicySignature: + - :string + - :rename: UploadPolicySignature + - :required + :output: + - bundleInstanceTask: + - startTime: + - :timestamp + - updateTime: + - :timestamp + CancelBundleTask: + :input: + BundleId: + - :string + - :required + :output: + - bundleInstanceTask: + - startTime: + - :timestamp + - updateTime: + - :timestamp + ConfirmProductInstance: + :input: + ProductCode: + - :string + - :required + InstanceId: + - :string + - :required + :output: [] + + CreateCustomerGateway: + :input: + Type: + - :string + - :required + IpAddress: + - :string + - :required + - :rename: PublicIp + BgpAsn: + - :integer + - :required + :output: + - customerGateway: + - tagSet: + - :list: item + CreateDhcpOptions: + :input: + DhcpConfiguration: + - :list: + - :structure: + Key: + - :string + - :rename: Key + ValueSet: + - :list: + - :string + - :rename: Values + - :required + - :rename: DhcpConfigurations + :output: + - dhcpOptions: + - dhcpConfigurationSet: + - :list: item + - item: + - valueSet: + - :list: item + - tagSet: + - :list: item + CreateKeyPair: + :input: + KeyName: + - :string + - :required + :output: [] + + CreateSecurityGroup: + :input: + GroupName: + - :string + - :required + GroupDescription: + - :string + - :required + - :rename: Description + VpcId: + - :string + :output: [] + + CreateSnapshot: + :input: + VolumeId: + - :string + - :required + Description: + - :string + :output: + - startTime: + - :timestamp + - volumeSize: + - :integer + - tagSet: + - :list: item + CreateSubnet: + :input: + VpcId: + - :string + - :required + CidrBlock: + - :string + - :required + AvailabilityZone: + - :string + :output: + - subnet: + - availableIpAddressCount: + - :integer + - tagSet: + - :list: item + CreateVolume: + :input: + Size: + - :integer + SnapshotId: + - :string + AvailabilityZone: + - :string + - :required + :output: + - size: + - :integer + - createTime: + - :timestamp + - attachmentSet: + - :list: item + - item: + - attachTime: + - :timestamp + - deleteOnTermination: + - :boolean + - tagSet: + - :list: item + CreateVpc: + :input: + CidrBlock: + - :string + - :required + InstanceTenancy: + - :string + :output: + - vpc: + - tagSet: + - :list: item + CreateVpnConnection: + :input: + Type: + - :string + - :required + CustomerGatewayId: + - :string + - :required + VpnGatewayId: + - :string + - :required + :output: + - vpnConnection: + - tagSet: + - :list: item + CreateVpnGateway: + :input: + Type: + - :string + - :required + AvailabilityZone: + - :string + :output: + - vpnGateway: + - attachments: + - :list: item + - tagSet: + - :list: item + DeleteCustomerGateway: + :input: + CustomerGatewayId: + - :string + - :required + :output: [] + + DeleteDhcpOptions: + :input: + DhcpOptionsId: + - :string + - :required + :output: [] + + DeleteKeyPair: + :input: + KeyName: + - :string + - :required + :output: [] + + DeleteSecurityGroup: + :input: + GroupName: + - :string + GroupId: + - :string + :output: [] + + DeleteSnapshot: + :input: + SnapshotId: + - :string + - :required + :output: [] + + DeleteSubnet: + :input: + SubnetId: + - :string + - :required + :output: [] + + DeleteVolume: + :input: + VolumeId: + - :string + - :required + :output: [] + + DeleteVpc: + :input: + VpcId: + - :string + - :required + :output: [] + + DeleteVpnConnection: + :input: + VpnConnectionId: + - :string + - :required + :output: [] + + DeleteVpnGateway: + :input: + VpnGatewayId: + - :string + - :required + :output: [] + + DeregisterImage: + :input: + ImageId: + - :string + - :required + :output: [] + + DescribeAddresses: + :input: + PublicIp: + - :list: + - :string + - :rename: PublicIps + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + AllocationId: + - :list: + - :string + - :rename: allocationIds + :output: + - addressesSet: + - :list: item + DescribeAvailabilityZones: + :input: + ZoneName: + - :list: + - :string + - :rename: ZoneNames + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - availabilityZoneInfo: + - :list: item + DescribeBundleTasks: + :input: + BundleId: + - :list: + - :string + - :rename: BundleIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - bundleInstanceTasksSet: + - :list: item + - item: + - startTime: + - :timestamp + - updateTime: + - :timestamp + DescribeCustomerGateways: + :input: + CustomerGatewayId: + - :list: + - :string + - :rename: CustomerGatewayIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: Filters + :output: + - customerGatewaySet: + - :list: item + - item: + - tagSet: + - :list: item + DescribeDhcpOptions: + :input: + DhcpOptionsId: + - :list: + - :string + - :rename: DhcpOptionsIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - dhcpOptionsSet: + - :list: item + - item: + - dhcpConfigurationSet: + - :list: item + - item: + - valueSet: + - :list: item + - tagSet: + - :list: item + DescribeImageAttribute: + :input: + ImageId: + - :string + - :required + Attribute: + - :string + - :required + :output: + - launchPermission: + - :list: item + - productCodes: + - :list: item + - blockDeviceMapping: + - :list: item + - item: + - ebs: + - volumeSize: + - :integer + - deleteOnTermination: + - :boolean + DescribeImages: + :input: + ImageId: + - :list: + - :string + - :rename: ImageIds + Owner: + - :list: + - :string + - :rename: Owners + ExecutableBy: + - :list: + - :string + - :rename: ExecutableUsers + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - imagesSet: + - :list: item + - item: + - isPublic: + - :boolean + - productCodes: + - :list: item + - blockDeviceMapping: + - :list: item + - item: + - ebs: + - volumeSize: + - :integer + - deleteOnTermination: + - :boolean + - tagSet: + - :list: item + DescribeInstances: + :input: + InstanceId: + - :list: + - :string + - :rename: InstanceIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - reservationSet: + - :list: item + - item: + - groupSet: + - :list: item + - instancesSet: + - :list: item + - item: + - instanceState: + - code: + - :integer + - amiLaunchIndex: + - :integer + - productCodes: + - :list: item + - launchTime: + - :timestamp + - blockDeviceMapping: + - :list: item + - item: + - ebs: + - attachTime: + - :timestamp + - deleteOnTermination: + - :boolean + - tagSet: + - :list: item + - groupSet: + - :list: item + - sourceDestCheck: + - :boolean + DescribeKeyPairs: + :input: + KeyName: + - :list: + - :string + - :rename: KeyNames + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - keySet: + - :list: item + DescribeRegions: + :input: + RegionName: + - :list: + - :string + - :rename: RegionNames + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - regionInfo: + - :list: item + DescribeReservedInstances: + :input: + ReservedInstancesId: + - :list: + - :string + - :rename: ReservedInstancesIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - reservedInstancesSet: + - :list: item + - item: + - start: + - :timestamp + - duration: + - :long + - usagePrice: + - :float + - fixedPrice: + - :float + - instanceCount: + - :integer + - tagSet: + - :list: item + DescribeReservedInstancesOfferings: + :input: + ReservedInstancesOfferingId: + - :list: + - :string + - :rename: ReservedInstancesOfferingIds + InstanceType: + - :string + AvailabilityZone: + - :string + ProductDescription: + - :string + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + InstanceTenancy: + - :string + :output: + - reservedInstancesOfferingsSet: + - :list: item + - item: + - duration: + - :long + - usagePrice: + - :float + - fixedPrice: + - :float + DescribeSecurityGroups: + :input: + GroupName: + - :list: + - :string + - :rename: GroupNames + GroupId: + - :list: + - :string + - :rename: GroupIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - securityGroupInfo: + - :list: item + - item: + - ipPermissions: + - :list: item + - item: + - fromPort: + - :integer + - toPort: + - :integer + - groups: + - :list: item + - ipRanges: + - :list: item + - ipPermissionsEgress: + - :list: item + - item: + - fromPort: + - :integer + - toPort: + - :integer + - groups: + - :list: item + - ipRanges: + - :list: item + - tagSet: + - :list: item + DescribeSnapshotAttribute: + :input: + SnapshotId: + - :string + - :required + Attribute: + - :string + - :required + :output: + - createVolumePermission: + - :list: item + DescribeSnapshots: + :input: + SnapshotId: + - :list: + - :string + - :rename: SnapshotIds + Owner: + - :list: + - :string + - :rename: OwnerIds + RestorableBy: + - :list: + - :string + - :rename: RestorableByUserIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - snapshotSet: + - :list: item + - item: + - startTime: + - :timestamp + - volumeSize: + - :integer + - tagSet: + - :list: item + DescribeSubnets: + :input: + SubnetId: + - :list: + - :string + - :rename: SubnetIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: Filters + :output: + - subnetSet: + - :list: item + - item: + - availableIpAddressCount: + - :integer + - tagSet: + - :list: item + DescribeVolumes: + :input: + VolumeId: + - :list: + - :string + - :rename: VolumeIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - volumeSet: + - :list: item + - item: + - size: + - :integer + - createTime: + - :timestamp + - attachmentSet: + - :list: item + - item: + - attachTime: + - :timestamp + - deleteOnTermination: + - :boolean + - tagSet: + - :list: item + DescribeVpcs: + :input: + VpcId: + - :list: + - :string + - :rename: VpcIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: Filters + :output: + - vpcSet: + - :list: item + - item: + - tagSet: + - :list: item + DescribeVpnConnections: + :input: + VpnConnectionId: + - :list: + - :string + - :rename: VpnConnectionIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: Filters + :output: + - vpnConnectionSet: + - :list: item + - item: + - tagSet: + - :list: item + DescribeVpnGateways: + :input: + VpnGatewayId: + - :list: + - :string + - :rename: VpnGatewayIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: Filters + :output: + - vpnGatewaySet: + - :list: item + - item: + - attachments: + - :list: item + - tagSet: + - :list: item + DetachVolume: + :input: + VolumeId: + - :string + - :required + InstanceId: + - :string + Device: + - :string + Force: + - :boolean + :output: + - attachTime: + - :timestamp + - deleteOnTermination: + - :boolean + DetachVpnGateway: + :input: + VpnGatewayId: + - :string + - :required + VpcId: + - :string + - :required + :output: [] + + DisassociateAddress: + :input: + PublicIp: + - :string + - :required + AssociationId: + - :string + :output: [] + + GetConsoleOutput: + :input: + InstanceId: + - :string + - :required + :output: + - timestamp: + - :timestamp + GetPasswordData: + :input: + InstanceId: + - :string + - :required + :output: + - timestamp: + - :timestamp + ImportKeyPair: + :input: + KeyName: + - :string + - :required + PublicKeyMaterial: + - :string + - :required + :output: [] + + ModifyImageAttribute: + :input: + ImageId: + - :string + - :required + Attribute: + - :string + OperationType: + - :string + UserId: + - :list: + - :string + - :rename: UserIds + UserGroup: + - :list: + - :string + - :rename: UserGroups + ProductCode: + - :list: + - :string + - :rename: ProductCodes + Value: + - :string + LaunchPermission: + - :structure: + Add: + - :list: + - :structure: + UserId: + - :string + - :rename: UserId + Group: + - :string + - :rename: Group + Remove: + - :list: + - :structure: + UserId: + - :string + - :rename: UserId + Group: + - :string + - :rename: Group + Description: + - :structure: + Value: + - :string + :output: [] + + ModifySnapshotAttribute: + :input: + SnapshotId: + - :string + - :required + Attribute: + - :string + OperationType: + - :string + UserId: + - :list: + - :string + - :rename: UserIds + UserGroup: + - :list: + - :string + - :rename: GroupNames + CreateVolumePermission: + - :structure: + Add: + - :list: + - :structure: + UserId: + - :string + - :rename: UserId + Group: + - :string + - :rename: Group + Remove: + - :list: + - :structure: + UserId: + - :string + - :rename: UserId + Group: + - :string + - :rename: Group + :output: [] + + MonitorInstances: + :input: + InstanceId: + - :list: + - :string + - :required + - :rename: InstanceIds + :output: + - instancesSet: + - :list: item + PurchaseReservedInstancesOffering: + :input: + ReservedInstancesOfferingId: + - :string + - :required + InstanceCount: + - :integer + - :required + :output: [] + + RebootInstances: + :input: + InstanceId: + - :list: + - :string + - :required + - :rename: InstanceIds + :output: [] + + RegisterImage: + :input: + ImageLocation: + - :string + Name: + - :string + Description: + - :string + Architecture: + - :string + KernelId: + - :string + RamdiskId: + - :string + RootDeviceName: + - :string + BlockDeviceMapping: + - :list: + - :structure: + VirtualName: + - :string + - :rename: VirtualName + DeviceName: + - :string + - :rename: DeviceName + Ebs: + - :structure: + SnapshotId: + - :string + VolumeSize: + - :integer + DeleteOnTermination: + - :boolean + NoDevice: + - :string + - :rename: blockDeviceMappings + :output: [] + + ReleaseAddress: + :input: + PublicIp: + - :string + AllocationId: + - :string + :output: [] + + ResetImageAttribute: + :input: + ImageId: + - :string + - :required + Attribute: + - :string + - :required + :output: [] + + ResetSnapshotAttribute: + :input: + SnapshotId: + - :string + - :required + Attribute: + - :string + - :required + :output: [] + + RevokeSecurityGroupIngress: + :input: + GroupName: + - :string + GroupId: + - :string + SourceSecurityGroupName: + - :string + SourceSecurityGroupOwnerId: + - :string + IpProtocol: + - :string + FromPort: + - :integer + ToPort: + - :integer + CidrIp: + - :string + IpPermissions: + - :list: + - :structure: + IpProtocol: + - :string + - :rename: IpProtocol + FromPort: + - :integer + - :rename: FromPort + ToPort: + - :integer + - :rename: ToPort + Groups: + - :list: + - :structure: + UserId: + - :string + - :rename: UserId + GroupName: + - :string + - :rename: GroupName + GroupId: + - :string + - :rename: GroupId + - :rename: UserIdGroupPairs + IpRanges: + - :list: + - :structure: + CidrIp: + - :string + - :rename: CidrIp + - :rename: IpRanges + :output: [] + + RunInstances: + :input: + ImageId: + - :string + - :required + MinCount: + - :integer + - :required + MaxCount: + - :integer + - :required + KeyName: + - :string + SecurityGroup: + - :list: + - :string + - :rename: SecurityGroups + SecurityGroupId: + - :list: + - :string + - :rename: SecurityGroupIds + UserData: + - :string + InstanceType: + - :string + Placement: + - :structure: + AvailabilityZone: + - :string + - :rename: AvailabilityZone + GroupName: + - :string + Tenancy: + - :string + KernelId: + - :string + RamdiskId: + - :string + BlockDeviceMapping: + - :list: + - :structure: + VirtualName: + - :string + - :rename: VirtualName + DeviceName: + - :string + - :rename: DeviceName + Ebs: + - :structure: + SnapshotId: + - :string + VolumeSize: + - :integer + DeleteOnTermination: + - :boolean + NoDevice: + - :string + - :rename: BlockDeviceMappings + Monitoring: + - :structure: + Enabled: + - :boolean + - :required + SubnetId: + - :string + DisableApiTermination: + - :boolean + InstanceInitiatedShutdownBehavior: + - :string + License: + - :structure: + Pool: + - :string + PrivateIpAddress: + - :string + ClientToken: + - :string + AdditionalInfo: + - :string + :output: + - groupSet: + - :list: item + - instancesSet: + - :list: item + - item: + - instanceState: + - code: + - :integer + - amiLaunchIndex: + - :integer + - productCodes: + - :list: item + - launchTime: + - :timestamp + - blockDeviceMapping: + - :list: item + - item: + - ebs: + - attachTime: + - :timestamp + - deleteOnTermination: + - :boolean + - tagSet: + - :list: item + - groupSet: + - :list: item + - sourceDestCheck: + - :boolean + TerminateInstances: + :input: + InstanceId: + - :list: + - :string + - :required + - :rename: InstanceIds + :output: + - instancesSet: + - :list: item + - item: + - currentState: + - code: + - :integer + - previousState: + - code: + - :integer + UnmonitorInstances: + :input: + InstanceId: + - :list: + - :string + - :required + - :rename: InstanceIds + :output: + - instancesSet: + - :list: item + CreateImage: + :input: + InstanceId: + - :string + - :required + Name: + - :string + - :required + Description: + - :string + NoReboot: + - :boolean + :output: [] + + StartInstances: + :input: + InstanceId: + - :list: + - :string + - :required + - :rename: InstanceIds + :output: + - instancesSet: + - :list: item + - item: + - currentState: + - code: + - :integer + - previousState: + - code: + - :integer + StopInstances: + :input: + InstanceId: + - :list: + - :string + - :required + - :rename: InstanceIds + Force: + - :boolean + :output: + - instancesSet: + - :list: item + - item: + - currentState: + - code: + - :integer + - previousState: + - code: + - :integer + DescribeInstanceAttribute: + :input: + InstanceId: + - :string + - :required + Attribute: + - :string + - :required + :output: + - disableApiTermination: + - value: + - :boolean + - blockDeviceMapping: + - :list: item + - item: + - ebs: + - attachTime: + - :timestamp + - deleteOnTermination: + - :boolean + ModifyInstanceAttribute: + :input: + InstanceId: + - :string + - :required + Attribute: + - :string + Value: + - :string + BlockDeviceMapping: + - :list: + - :structure: + DeviceName: + - :string + Ebs: + - :structure: + VolumeId: + - :string + DeleteOnTermination: + - :boolean + VirtualName: + - :string + NoDevice: + - :string + - :rename: blockDeviceMappings + SourceDestCheck: + - :structure: + Value: + - :boolean + DisableApiTermination: + - :structure: + Value: + - :boolean + InstanceType: + - :structure: + Value: + - :string + Kernel: + - :structure: + Value: + - :string + Ramdisk: + - :structure: + Value: + - :string + UserData: + - :structure: + Value: + - :string + InstanceInitiatedShutdownBehavior: + - :structure: + Value: + - :string + GroupId: + - :list: + - :string + - :rename: groups + :output: [] + + ResetInstanceAttribute: + :input: + InstanceId: + - :string + - :required + Attribute: + - :string + - :required + :output: [] + + RequestSpotInstances: + :input: + SpotPrice: + - :string + - :required + InstanceCount: + - :integer + Type: + - :string + ValidFrom: + - :timestamp + ValidUntil: + - :timestamp + LaunchGroup: + - :string + AvailabilityZoneGroup: + - :string + LaunchSpecification: + - :structure: + ImageId: + - :string + KeyName: + - :string + GroupSet: + - :list: + - :structure: + GroupName: + - :string + - :rename: GroupName + GroupId: + - :string + - :rename: SecurityGroups + UserData: + - :string + AddressingType: + - :string + InstanceType: + - :string + Placement: + - :structure: + AvailabilityZone: + - :string + GroupName: + - :string + KernelId: + - :string + RamdiskId: + - :string + BlockDeviceMapping: + - :list: + - :structure: + VirtualName: + - :string + - :rename: VirtualName + DeviceName: + - :string + - :rename: DeviceName + Ebs: + - :structure: + SnapshotId: + - :string + VolumeSize: + - :integer + DeleteOnTermination: + - :boolean + NoDevice: + - :string + - :rename: blockDeviceMappings + MonitoringEnabled: + - :boolean + SubnetId: + - :string + :output: + - spotInstanceRequestSet: + - :list: item + - item: + - validFrom: + - :timestamp + - validUntil: + - :timestamp + - launchSpecification: + - groupSet: + - :list: item + - blockDeviceMapping: + - :list: item + - item: + - ebs: + - volumeSize: + - :integer + - deleteOnTermination: + - :boolean + - monitoringEnabled: + - :boolean + - createTime: + - :timestamp + - tagSet: + - :list: item + DescribeSpotInstanceRequests: + :input: + SpotInstanceRequestId: + - :list: + - :string + - :rename: spotInstanceRequestIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - spotInstanceRequestSet: + - :list: item + - item: + - validFrom: + - :timestamp + - validUntil: + - :timestamp + - launchSpecification: + - groupSet: + - :list: item + - blockDeviceMapping: + - :list: item + - item: + - ebs: + - volumeSize: + - :integer + - deleteOnTermination: + - :boolean + - monitoringEnabled: + - :boolean + - createTime: + - :timestamp + - tagSet: + - :list: item + CancelSpotInstanceRequests: + :input: + SpotInstanceRequestId: + - :list: + - :string + - :required + - :rename: spotInstanceRequestIds + :output: + - spotInstanceRequestSet: + - :list: item + DescribeSpotPriceHistory: + :input: + StartTime: + - :timestamp + EndTime: + - :timestamp + InstanceType: + - :list: + - :string + - :rename: instanceTypes + ProductDescription: + - :list: + - :string + - :rename: productDescriptions + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + AvailabilityZone: + - :string + MaxResults: + - :integer + NextToken: + - :string + :output: + - spotPriceHistorySet: + - :list: item + - item: + - timestamp: + - :timestamp + CreateSpotDatafeedSubscription: + :input: + Bucket: + - :string + - :required + Prefix: + - :string + :output: [] + + DescribeSpotDatafeedSubscription: + :input: {} + + :output: [] + + DeleteSpotDatafeedSubscription: + :input: {} + + :output: [] + + DescribeLicenses: + :input: + LicenseId: + - :list: + - :string + - :rename: licenseIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - licenseSet: + - :list: item + - item: + - capacitySet: + - :list: item + - item: + - capacity: + - :integer + - instanceCapacity: + - :integer + - earliestAllowedDeactivationTime: + - :timestamp + - tagSet: + - :list: item + ActivateLicense: + :input: + LicenseId: + - :string + - :required + Capacity: + - :integer + - :required + :output: [] + + DeactivateLicense: + :input: + LicenseId: + - :string + - :required + Capacity: + - :integer + - :required + :output: [] + + CreatePlacementGroup: + :input: + GroupName: + - :string + - :required + Strategy: + - :string + - :required + :output: [] + + DeletePlacementGroup: + :input: + GroupName: + - :string + - :required + :output: [] + + DescribePlacementGroups: + :input: + GroupName: + - :list: + - :string + - :rename: groupNames + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - placementGroupSet: + - :list: item + CreateTags: + :input: + ResourceId: + - :list: + - :string + - :required + - :rename: resources + Tag: + - :list: + - :structure: + Key: + - :string + Value: + - :string + - :required + - :rename: tags + :output: [] + + DescribeTags: + :input: + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - tagSet: + - :list: item + DeleteTags: + :input: + ResourceId: + - :list: + - :string + - :required + - :rename: resources + Tag: + - :list: + - :structure: + Key: + - :string + Value: + - :string + - :rename: tags + :output: [] + + AuthorizeSecurityGroupEgress: + :input: + GroupId: + - :string + - :required + SourceSecurityGroupName: + - :string + SourceSecurityGroupOwnerId: + - :string + IpProtocol: + - :string + FromPort: + - :integer + ToPort: + - :integer + CidrIp: + - :string + IpPermissions: + - :list: + - :structure: + IpProtocol: + - :string + - :rename: IpProtocol + FromPort: + - :integer + - :rename: FromPort + ToPort: + - :integer + - :rename: ToPort + Groups: + - :list: + - :structure: + UserId: + - :string + - :rename: UserId + GroupName: + - :string + - :rename: GroupName + GroupId: + - :string + - :rename: GroupId + - :rename: UserIdGroupPairs + IpRanges: + - :list: + - :structure: + CidrIp: + - :string + - :rename: CidrIp + - :rename: IpRanges + :output: [] + + RevokeSecurityGroupEgress: + :input: + GroupId: + - :string + - :required + SourceSecurityGroupName: + - :string + SourceSecurityGroupOwnerId: + - :string + IpProtocol: + - :string + FromPort: + - :integer + ToPort: + - :integer + CidrIp: + - :string + IpPermissions: + - :list: + - :structure: + IpProtocol: + - :string + - :rename: IpProtocol + FromPort: + - :integer + - :rename: FromPort + ToPort: + - :integer + - :rename: ToPort + Groups: + - :list: + - :structure: + UserId: + - :string + - :rename: UserId + GroupName: + - :string + - :rename: GroupName + GroupId: + - :string + - :rename: GroupId + - :rename: UserIdGroupPairs + IpRanges: + - :list: + - :structure: + CidrIp: + - :string + - :rename: CidrIp + - :rename: IpRanges + :output: [] + + CreateInternetGateway: + :input: {} + + :output: + - internetGateway: + - attachmentSet: + - :list: item + - tagSet: + - :list: item + DescribeInternetGateways: + :input: + InternetGatewayId: + - :list: + - :string + - :rename: internetGatewayIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - internetGatewaySet: + - :list: item + - item: + - attachmentSet: + - :list: item + - tagSet: + - :list: item + DeleteInternetGateway: + :input: + InternetGatewayId: + - :string + - :required + :output: [] + + AttachInternetGateway: + :input: + InternetGatewayId: + - :string + - :required + VpcId: + - :string + - :required + :output: [] + + DetachInternetGateway: + :input: + InternetGatewayId: + - :string + - :required + VpcId: + - :string + - :required + :output: [] + + CreateRouteTable: + :input: + VpcId: + - :string + - :required + :output: + - routeTable: + - routeSet: + - :list: item + - associationSet: + - :list: item + - item: + - main: + - :boolean + - tagSet: + - :list: item + DescribeRouteTables: + :input: + RouteTableId: + - :list: + - :string + - :rename: routeTableIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - routeTableSet: + - :list: item + - item: + - routeSet: + - :list: item + - associationSet: + - :list: item + - item: + - main: + - :boolean + - tagSet: + - :list: item + DeleteRouteTable: + :input: + RouteTableId: + - :string + - :required + :output: [] + + AssociateRouteTable: + :input: + SubnetId: + - :string + - :required + RouteTableId: + - :string + - :required + :output: [] + + ReplaceRouteTableAssociation: + :input: + AssociationId: + - :string + - :required + RouteTableId: + - :string + - :required + :output: [] + + DisassociateRouteTable: + :input: + AssociationId: + - :string + - :required + :output: [] + + CreateRoute: + :input: + RouteTableId: + - :string + - :required + DestinationCidrBlock: + - :string + - :required + GatewayId: + - :string + InstanceId: + - :string + :output: [] + + ReplaceRoute: + :input: + RouteTableId: + - :string + - :required + DestinationCidrBlock: + - :string + - :required + GatewayId: + - :string + InstanceId: + - :string + :output: [] + + DeleteRoute: + :input: + RouteTableId: + - :string + - :required + DestinationCidrBlock: + - :string + - :required + :output: [] + + CreateNetworkAcl: + :input: + VpcId: + - :string + - :required + :output: + - networkAcl: + - default: + - :boolean + - entrySet: + - :list: item + - item: + - ruleNumber: + - :integer + - egress: + - :boolean + - icmpTypeCode: + - type: + - :integer + - code: + - :integer + - portRange: + - from: + - :integer + - to: + - :integer + - associationSet: + - :list: item + - tagSet: + - :list: item + DescribeNetworkAcls: + :input: + NetworkAclId: + - :list: + - :string + - :rename: networkAclIds + Filter: + - :list: + - :structure: + Name: + - :string + Value: + - :list: + - :string + - :rename: Values + - :rename: filters + :output: + - networkAclSet: + - :list: item + - item: + - default: + - :boolean + - entrySet: + - :list: item + - item: + - ruleNumber: + - :integer + - egress: + - :boolean + - icmpTypeCode: + - type: + - :integer + - code: + - :integer + - portRange: + - from: + - :integer + - to: + - :integer + - associationSet: + - :list: item + - tagSet: + - :list: item + DeleteNetworkAcl: + :input: + NetworkAclId: + - :string + - :required + :output: [] + + ReplaceNetworkAclAssociation: + :input: + AssociationId: + - :string + - :required + NetworkAclId: + - :string + - :required + :output: [] + + CreateNetworkAclEntry: + :input: + NetworkAclId: + - :string + - :required + RuleNumber: + - :integer + - :required + Protocol: + - :string + - :required + RuleAction: + - :string + - :required + Egress: + - :boolean + - :required + CidrBlock: + - :string + - :required + Icmp: + - :structure: + Type: + - :integer + Code: + - :integer + - :rename: icmpTypeCode + PortRange: + - :structure: + From: + - :integer + To: + - :integer + :output: [] + + ReplaceNetworkAclEntry: + :input: + NetworkAclId: + - :string + - :required + RuleNumber: + - :integer + - :required + Protocol: + - :string + - :required + RuleAction: + - :string + - :required + Egress: + - :boolean + - :required + CidrBlock: + - :string + - :required + Icmp: + - :structure: + Type: + - :integer + Code: + - :integer + - :rename: icmpTypeCode + PortRange: + - :structure: + From: + - :integer + To: + - :integer + :output: [] + + DeleteNetworkAclEntry: + :input: + NetworkAclId: + - :string + - :required + RuleNumber: + - :integer + - :required + Egress: + - :boolean + - :required + :output: [] + +:client_errors: {} + +:server_errors: {} diff --git a/lib/aws/api_config/SNS-2010-03-31.yml b/lib/aws/api_config/SNS-2010-03-31.yml new file mode 100644 index 00000000000..fd2c8dadf05 --- /dev/null +++ b/lib/aws/api_config/SNS-2010-03-31.yml @@ -0,0 +1,171 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +--- +:operations: + CreateTopic: + :input: + Name: + - :string + - :required + :output: + - TopicArn: [] + + GetTopicAttributes: + :input: + TopicArn: + - :string + - :required + :output: + - Attributes: + - :map: + - entry + - key + - value + SetTopicAttributes: + :input: + TopicArn: + - :string + - :required + AttributeName: + - :string + - :required + AttributeValue: + - :string + - :required + :output: [] + + DeleteTopic: + :input: + TopicArn: + - :string + - :required + :output: [] + + AddPermission: + :input: + TopicArn: + - :string + - :required + Label: + - :string + - :required + AWSAccountId: + - :list: + - :string + - :required + ActionName: + - :list: + - :string + - :required + :output: [] + + RemovePermission: + :input: + TopicArn: + - :string + - :required + Label: + - :string + - :required + :output: [] + + Subscribe: + :input: + TopicArn: + - :string + - :required + Protocol: + - :string + - :required + Endpoint: + - :string + - :required + :output: [] + + Unsubscribe: + :input: + SubscriptionArn: + - :string + - :required + :output: [] + + ConfirmSubscription: + :input: + TopicArn: + - :string + - :required + Token: + - :string + - :required + AuthenticateOnUnsubscribe: + - :boolean + :output: [] + + ListTopics: + :input: + NextToken: + - :string + :output: + - Topics: + - :list: member + - NextToken: + - :force + ListSubscriptions: + :input: + NextToken: + - :string + :output: + - Subscriptions: + - :list: member + - NextToken: + - :force + ListSubscriptionsByTopic: + :input: + TopicArn: + - :string + - :required + NextToken: + - :string + :output: + - Subscriptions: + - :list: member + - NextToken: + - :force + Publish: + :input: + TopicArn: + - :string + - :required + Message: + - :string + - :required + Subject: + - :string + MessageStructure: + - :string + :output: [] + +:client_errors: + InvalidParameter: [] + + AuthorizationError: [] + + TopicLimitExceeded: [] + + SubscriptionLimitExceeded: [] + + NotFound: [] + +:server_errors: + InternalError: [] diff --git a/lib/aws/api_config/SQS-2009-02-01.yml b/lib/aws/api_config/SQS-2009-02-01.yml new file mode 100644 index 00000000000..22d1f1451f5 --- /dev/null +++ b/lib/aws/api_config/SQS-2009-02-01.yml @@ -0,0 +1,161 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +--- +:operations: + CreateQueue: + :input: + QueueName: + - :string + - :required + DefaultVisibilityTimeout: + - :integer + :output: [] + + ListQueues: + :input: + QueueNamePrefix: + - :string + :output: + - QueueUrl: + - :list + - :rename: queueUrls + AddPermission: + :input: + QueueUrl: + - :string + - :required + Label: + - :string + - :required + AWSAccountId: + - :list: + - :string + - :required + - :rename: AWSAccountIds + ActionName: + - :list: + - :string + - :required + - :rename: Actions + :output: [] + + RemovePermission: + :input: + QueueUrl: + - :string + - :required + Label: + - :string + - :required + :output: [] + + ChangeMessageVisibility: + :input: + QueueUrl: + - :string + - :required + ReceiptHandle: + - :string + - :required + VisibilityTimeout: + - :integer + - :required + :output: [] + + DeleteQueue: + :input: + QueueUrl: + - :string + - :required + :output: [] + + DeleteMessage: + :input: + QueueUrl: + - :string + - :required + ReceiptHandle: + - :string + - :required + :output: [] + + GetQueueAttributes: + :input: + QueueUrl: + - :string + - :required + AttributeName: + - :list: + - :string + - :rename: attributeNames + :output: + - Attribute: + - :rename: attributes + - :map_entry: + - Name + - Value + ReceiveMessage: + :input: + QueueUrl: + - :string + - :required + AttributeName: + - :list: + - :string + - :rename: AttributeNames + MaxNumberOfMessages: + - :integer + VisibilityTimeout: + - :integer + :output: + - Message: + - :list + - :rename: messages + - Attribute: + - :rename: Attributes + - :map_entry: + - Name + - Value + SetQueueAttributes: + :input: + QueueUrl: + - :string + - :required + Attribute: + - :required + - :structure: + Name: + - :string + - :required + Value: + - :string + - :required + :output: [] + + SendMessage: + :input: + QueueUrl: + - :string + - :required + MessageBody: + - :string + - :required + :output: [] + +:client_errors: + AWS.SimpleQueueService.QueueDeletedRecently: [] + + AWS.SimpleQueueService.QueueNameExists: [] + +:server_errors: {} diff --git a/lib/aws/api_config/SimpleDB-2009-04-15.yml b/lib/aws/api_config/SimpleDB-2009-04-15.yml new file mode 100644 index 00000000000..001806907a8 --- /dev/null +++ b/lib/aws/api_config/SimpleDB-2009-04-15.yml @@ -0,0 +1,278 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +--- +:operations: + DeleteAttributes: + :output: [] + + :input: + Expected: + - :list: + - :structure: + Name: + - :string + Exists: + - :boolean + Value: + - :string + Attribute: + - :list: + - :structure: + AlternateNameEncoding: + - :string + Name: + - :string + - :required + AlternateValueEncoding: + - :string + Value: + - :string + - :rename: Attributes + DomainName: + - :string + - :required + ItemName: + - :string + - :required + CreateDomain: + :output: [] + + :input: + DomainName: + - :string + - :required + ListDomains: + :output: + - DomainName: + - :list + - :rename: DomainNames + :input: + MaxNumberOfDomains: + - :integer + NextToken: + - :string + GetAttributes: + :output: + - Attribute: + - :list + - :rename: Attributes + :input: + DomainName: + - :string + - :required + AttributeName: + - :list: + - :string + - :rename: AttributeNames + ConsistentRead: + - :boolean + ItemName: + - :string + - :required + BatchPutAttributes: + :output: [] + + :input: + Item: + - :list: + - :structure: + Attribute: + - :list: + - :structure: + Replace: + - :boolean + Name: + - :string + - :required + Value: + - :string + - :required + - :required + - :rename: Attributes + ItemName: + - :string + - :required + - :rename: Name + - :required + - :rename: Items + DomainName: + - :string + - :required + DeleteDomain: + :output: [] + + :input: + DomainName: + - :string + - :required + Select: + :output: + - Item: + - :list + - :rename: Items + - Attribute: + - :list + - :rename: Attributes + :input: + SelectExpression: + - :string + - :required + ConsistentRead: + - :boolean + NextToken: + - :string + PutAttributes: + :output: [] + + :input: + Expected: + - :list: + - :structure: + Name: + - :string + Exists: + - :boolean + Value: + - :string + Attribute: + - :list: + - :structure: + Replace: + - :boolean + Name: + - :string + - :required + Value: + - :string + - :required + - :required + - :rename: Attributes + DomainName: + - :string + - :required + ItemName: + - :string + - :required + DomainMetadata: + :output: + - ItemCount: + - :integer + - ItemNamesSizeBytes: + - :long + - AttributeNameCount: + - :integer + - AttributeNamesSizeBytes: + - :long + - AttributeValueCount: + - :integer + - AttributeValuesSizeBytes: + - :long + - Timestamp: + - :integer + :input: + DomainName: + - :string + - :required + BatchDeleteAttributes: + :output: [] + + :input: + Item: + - :list: + - :structure: + Attribute: + - :list: + - :structure: + AlternateNameEncoding: + - :string + Name: + - :string + - :required + AlternateValueEncoding: + - :string + Value: + - :string + - :required + - :rename: Attributes + ItemName: + - :string + - :required + - :rename: Name + - :required + - :rename: Items + DomainName: + - :string + - :required +:client_errors: + TooManyRequestedAttributes: + - BoxUsage: + - :float + ConditionalCheckFailed: + - BoxUsage: + - :float + InvalidQueryExpression: + - BoxUsage: + - :float + InvalidParameterValue: + - BoxUsage: + - :float + DuplicateItemName: + - BoxUsage: + - :float + NumberDomainAttributesExceeded: + - BoxUsage: + - :float + InvalidNextToken: + - BoxUsage: + - :float + NumberSubmittedItemsExceeded: + - BoxUsage: + - :float + NoSuchDomain: + - BoxUsage: + - :float + AttributeDoesNotExist: + - BoxUsage: + - :float + NumberDomainsExceeded: + - BoxUsage: + - :float + MissingParameter: + - BoxUsage: + - :float + InvalidNumberValueTests: + - BoxUsage: + - :float + UnsupportedNextToken: + - BoxUsage: + - :float + NumberSubmittedAttributesExceeded: + - BoxUsage: + - :float + NumberItemAttributesExceeded: + - BoxUsage: + - :float + NumberDomainBytesExceeded: + - BoxUsage: + - :float + InvalidNumberPredicates: + - BoxUsage: + - :float + RequestTimeout: + - BoxUsage: + - :float + InvalidParameterCombination: + - BoxUsage: + - :float +:server_errors: {} diff --git a/lib/aws/api_config/SimpleEmailService-2010-12-01.yml b/lib/aws/api_config/SimpleEmailService-2010-12-01.yml new file mode 100644 index 00000000000..fb1b69d3efe --- /dev/null +++ b/lib/aws/api_config/SimpleEmailService-2010-12-01.yml @@ -0,0 +1,147 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +--- +:operations: + SendEmail: + :input: + Source: + - :string + - :required + Destination: + - :structure: + ToAddresses: + - :list: + - :string + CcAddresses: + - :list: + - :string + BccAddresses: + - :list: + - :string + - :required + Message: + - :structure: + Subject: + - :structure: + Data: + - :string + - :required + Charset: + - :string + - :required + Body: + - :structure: + Text: + - :structure: + Data: + - :string + - :required + Charset: + - :string + Html: + - :structure: + Data: + - :string + - :required + Charset: + - :string + - :required + - :required + ReplyToAddresses: + - :list: + - :string + ReturnPath: + - :string + :output: [] + + SendRawEmail: + :input: + Source: + - :string + Destinations: + - :list: + - :string + RawMessage: + - :structure: + Data: + - :blob + - :required + - :required + :output: [] + + GetSendStatistics: + :input: {} + + :output: + - SendDataPoints: + - :list: member + - member: + - Timestamp: + - :timestamp + - DeliveryAttempts: + - :long + - Bounces: + - :long + - Complaints: + - :long + - Rejects: + - :long + GetSendQuota: + :input: {} + + :output: + - Max24HourSend: + - :float + - MaxSendRate: + - :float + - SentLast24Hours: + - :float + VerifyEmailAddress: + :input: + EmailAddress: + - :string + - :required + :output: [] + + ListVerifiedEmailAddresses: + :input: {} + + :output: + - VerifiedEmailAddresses: + - :list: member + DeleteVerifiedEmailAddress: + :input: + EmailAddress: + - :string + - :required + :output: [] + +:client_errors: + MessageRejected: [] + + MalformedQueryString: [] + + InvalidParameterCombination: [] + + InvalidParameterValue: [] + + InvalidQueryParameter: [] + + MissingAction: [] + + MissingParameter: [] + + RequestExpired: [] + +:server_errors: {} diff --git a/lib/aws/api_config_transform.rb b/lib/aws/api_config_transform.rb new file mode 100644 index 00000000000..bd7218703ee --- /dev/null +++ b/lib/aws/api_config_transform.rb @@ -0,0 +1,32 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + + # @private + class ApiConfigTransform + + def self.rename_input_list_to_membered_list api_config + api_config[:operations].each_pair do |name,customizations| + + input = api_config[:operations][name][:input] + fixed_input = input.to_yaml.gsub(/:list:/, ':membered_list:') + + api_config[:operations][name][:input] = YAML.load(fixed_input) + + end + api_config + end + + end +end diff --git a/lib/aws/async_handle.rb b/lib/aws/async_handle.rb new file mode 100644 index 00000000000..1cb9f744a82 --- /dev/null +++ b/lib/aws/async_handle.rb @@ -0,0 +1,90 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + + # Mixin that provides a generic callback facility for asynchronous + # tasks that can either succeed or fail. + # @private + module AsyncHandle + + # Called to signal success and fire off the success and complete callbacks. + def signal_success + __send_signal(:success) + end + + # Called to signal failure and fire off the failure and complete callbacks. + def signal_failure + __send_signal(:failure) + end + + # Registers a callback to be called on successful completion of + # the task. + # + # handle.on_success { puts "It worked!" } + # + # If this is called when the task has already completed + # successfully, it will call the callback immediately. + def on_success(&blk) + if @_async_status == :success + blk.call + else + (@_async_callbacks ||= []) << { :success => blk } + end + end + + # Registers a callback to be called when the task fails. + # + # handle.on_failure { puts "It didn't work!" } + # + # If this is called when the task has already failed, it will + # call the callback immediately. + def on_failure(&blk) + if @_async_status == :failure + blk.call + else + (@_async_callbacks ||= []) << { :failure => blk } + end + end + + # Registers a callback to be called when the task is complete, + # regardless of its status. Yields the status to the block. + # + # handle.on_complete do |status| + # puts "It #{status == :success ? 'did' : 'did not'} work!" + # end + # + # If this is called when the task has already completed, it will + # call the callback immediately. + def on_complete(&blk) + if !@_async_status.nil? + blk.call(@_async_status) + else + (@_async_callbacks ||= []) << { + :failure => lambda { blk.call(:failure) }, + :success => lambda { blk.call(:success) } + } + end + end + + private + def __send_signal(kind) + @_async_status = kind + @_async_callbacks.map do |cb| + cb[kind] + end.compact.each { |blk| blk.call } if @_async_callbacks + end + + end + +end diff --git a/lib/aws/authorize_v2.rb b/lib/aws/authorize_v2.rb new file mode 100644 index 00000000000..169f434c1a9 --- /dev/null +++ b/lib/aws/authorize_v2.rb @@ -0,0 +1,37 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + + # Mixed into clients that use v2 authorization. + # @private + module AuthorizeV2 + + def string_to_sign + parts = [http_method, + host, + path, + params.sort.collect { |p| p.encoded }.join('&')] + parts.join("\n") + end + + def add_authorization! signer + self.access_key_id = signer.access_key_id + add_param('AWSAccessKeyId', access_key_id) + add_param('SignatureVersion', '2') + add_param('SignatureMethod', 'HmacSHA256') + add_param('Signature', signer.sign(string_to_sign)) + end + + end +end diff --git a/lib/aws/authorize_v3.rb b/lib/aws/authorize_v3.rb new file mode 100644 index 00000000000..238a5a76264 --- /dev/null +++ b/lib/aws/authorize_v3.rb @@ -0,0 +1,37 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'base64' +require 'time' + +module AWS + + # Mixed into clients that use v3 authorization. + # @private + module AuthorizeV3 + + def string_to_sign + headers['date'] ||= Time.now.rfc822 + end + + def add_authorization! signer + self.access_key_id = signer.access_key_id + parts = [] + parts << "AWS3-HTTPS AWSAccessKeyId=#{access_key_id}" + parts << "Algorithm=HmacSHA256" + parts << "Signature=#{signer.sign(string_to_sign)}" + headers['x-amzn-authorization'] = parts.join(',') + end + + end +end diff --git a/lib/aws/base_client.rb b/lib/aws/base_client.rb new file mode 100644 index 00000000000..718f8b78f84 --- /dev/null +++ b/lib/aws/base_client.rb @@ -0,0 +1,524 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/errors' +require 'aws/inflection' +require 'aws/naming' +require 'aws/response' +require 'aws/async_handle' +require 'aws/configurable' +require 'aws/http/handler' +require 'aws/http/request' +require 'aws/http/response' +require 'aws/xml_grammar' +require 'aws/option_grammar' +require 'benchmark' +require 'set' + +module AWS + + # Base class for all of the Amazon AWS service clients. + # @private + class BaseClient + + include Configurable + + extend Naming + + CACHEABLE_REQUESTS = Set.new + + # Creates a new low-level client. + # + # == Required Options + # + # To create a client you must provide access to AWS credentials. + # There are two options: + # + # * +:signer+ -- An object that responds to +access_key_id+ + # (to return the AWS Access Key ID) and to + # sign(string_to_sign) (to return a signature + # for a given string). An example implementation is + # AWS::DefaultSigner. This option is useful if you want to + # more tightly control access to your secret access key (for + # example by moving the signature computation into a + # different process). + # + # * +:access_key_id+ and +:secret_access_key+ -- You can use + # these options to provide the AWS Access Key ID and AWS + # Secret Access Key directly to the client. + # + # == Optional + # + # * +:http_handler+ -- Any object that implements a + # handle(request, response) method; an example + # is BuiltinHttpHandler. This method is used to perform the + # HTTP requests that this client constructs. + # + def initialize options = {} + + if options[:endpoint] + options[:"#{self.class.service_ruby_name}_endpoint"] = + options.delete(:endpoint) + end + + @config = options[:config] + @config ||= AWS.config + @config = @config.with(options) + @signer = @config.signer + @http_handler = @config.http_handler + @stubs = {} + + end + + # @return [Configuration] This clients configuration. + attr_reader :config + + # @return [DefaultSigner,Object] Returns the signer for this client. + # This is normally a DefaultSigner, but it can be configured to + # an other object. + attr_reader :signer + + # @return [String] the configured endpoint for this client. + def endpoint + config.send(:"#{self.class.service_ruby_name}_endpoint") + end + + # Returns a copy of the client with a different HTTP handler. + # You can pass an object like BuiltinHttpHandler or you can + # use a block; for example: + # + # s3_with_logging = s3.with_http_handler do |request, response| + # $stderr.puts request.inspect + # super + # end + # + # The block executes in the context of an HttpHandler + # instance, and +super+ delegates to the HTTP handler used by + # this client. This provides an easy way to spy on requests + # and responses. See HttpHandler, HttpRequest, and + # HttpResponse for more details on how to implement a fully + # functional HTTP handler using a different HTTP library than + # the one that ships with Ruby. + # @param handler (nil) A new http handler. Leave blank and pass a + # block to wrap the current handler with the block. + # @return [BaseClient] Returns a new instance of the client class with + # the modified or wrapped http handler. + def with_http_handler(handler = nil, &blk) + handler ||= Http::Handler.new(@http_handler, &blk) + with_options(:http_handler => handler) + end + + # @param [Hash] options + # @see AWS.config detailed list of accepted options. + def with_options options + with_config(config.with(options)) + end + + # @param [Configuration] The configuration object to use. + # @return [BaseClient] Returns a new client object with the given + # configuration. + def with_config config + self.class.new(:config => config) + end + + # The stub returned is memoized. + # @see new_stub_for + # @private + def stub_for method_name + @stubs[method_name] ||= new_stub_for(method_name) + end + + # Primarily used for testing, this method returns an empty psuedo + # service response without making a request. Its used primarily for + # testing the ligher level service interfaces. + # @private + def new_stub_for method_name + response = Response.new(Http::Request.new, Http::Response.new) + response.request_type = method_name + response.request_options = {} + send("simulate_#{method_name}_response", response) + response.signal_success + response + end + + protected + def new_request + req = self.class::REQUEST_CLASS.new + req.http_method = 'POST' + req.headers['Content-Type'] = 'application/x-www-form-urlencoded' + req.add_param 'Timestamp', Time.now.utc.strftime('%Y-%m-%dT%TZ') + req.add_param 'Version', self.class::API_VERSION + req + end + + protected + def new_response(*args) + Response.new(*args) + end + + private + def log severity, message + config.logger.send(severity, message + "\n") if config.logger + end + + private + def log_client_request method_name, options, &block + + response = nil + time = Benchmark.measure do + response = yield + end + + if options[:async] + response.on_success { log_client_request_on_success(method_name, + options, + response, + time) } + else + log_client_request_on_success(method_name, + options, + response, + time) + end + + response + + end + + private + def log_client_request_on_success(method_name, options, response, time) + status = response.http_response.status + service = self.class.service_name + + pattern = "[AWS %s %s %.06f] %s %s" + parts = [service, status, time.real, method_name, options.inspect] + severity = :info + + if response.error + pattern += " %s: %s" + parts << response.error.class + parts << response.error.message + severity = :error + end + + if response.cached + pattern << " [CACHED]" + end + + log(severity, pattern % parts) + end + + private + def make_async_request response + + pauses = async_request_with_retries(response, + response.http_request) + + response + + end + + private + def async_request_with_retries response, http_request, retry_delays = nil + + response.http_response = AWS::Http::Response.new + + handle = Object.new + handle.extend AsyncHandle + handle.on_complete do |status| + case status + when :failure + response.error = StandardError.new("failed to contact the service") + response.signal_failure + when :success + populate_error(response) + retry_delays ||= sleep_durations(response) + if should_retry?(response) and !retry_delays.empty? + @http_handler.sleep_with_callback(retry_delays.shift) do + async_request_with_retries(response, http_request, retry_delays) + end + else + response.error ? + response.signal_failure : + response.signal_success + end + end + end + + @http_handler.handle_async(http_request, response.http_response, handle) + + end + + private + def make_sync_request response + retry_server_errors do + + response.http_response = http_response = + AWS::Http::Response.new + + @http_handler.handle(response.http_request, http_response) + + populate_error(response) + response.signal_success unless response.error + response + + end + end + + private + def retry_server_errors &block + + response = yield + + sleeps = sleep_durations(response) + while should_retry?(response) + break if sleeps.empty? + Kernel.sleep(sleeps.shift) + response = yield + end + + response + + end + + private + def sleep_durations response + factor = scaling_factor(response) + Array.new(config.max_retries) {|n| (2 ** n) * factor } + end + + private + def scaling_factor response + response.throttled? ? (0.5 + Kernel.rand * 0.1) : 0.3 + end + + private + def should_retry? response + response.timeout? or + response.throttled? or + response.error.kind_of?(Errors::ServerError) + end + + private + def return_or_raise options, &block + response = yield + unless options[:async] + raise response.error if response.error + end + response + end + + protected + def populate_error response + + # clear out a previous error + response.error = nil + status = response.http_response.status + code = nil + code = xml_error_grammar.parse(response.http_response.body).code if + xml_error_response?(response) + + case + when response.timeout? + response.error = Timeout::Error.new + + when code + response.error = + service_module::Errors.error_class(code).new(response.http_request, + response.http_response) + when status >= 500 + response.error = + Errors::ServerError.new(response.http_request, response.http_response) + + when status >= 300 + response.error = + Errors::ClientError.new(response.http_request, response.http_response) + end + + end + + protected + def xml_error_response? response + response.http_response.status >= 300 and + xml_error_grammar.parse(response.http_response.body).respond_to?(:code) + + end + + protected + def xml_error_grammar + if service_module::const_defined?(:Errors) and + service_module::Errors::const_defined?(:BASE_ERROR_GRAMMAR) + service_module::Errors::BASE_ERROR_GRAMMAR + else + XmlGrammar + end + end + + protected + def service_module + AWS.const_get(self.class.to_s[/(\w+)::Client/, 1]) + end + + private + def client_request name, options, &block + return_or_raise(options) do + log_client_request(name, options) do + + # we dont want to pass the async option to the configure block + opts = options.dup + opts.delete(:async) + + # configure the http request + http_request = new_request + http_request.host = endpoint + http_request.use_ssl = config.use_ssl? + send("configure_#{name}_request", http_request, opts, &block) + http_request.headers["user-agent"] = user_agent_string + http_request.add_authorization!(signer) + + if config.stub_requests? + + response = stub_for(name) + response.http_request = http_request + response.request_options = options + response + + else + + response = new_response(http_request) + response.request_type = name + response.request_options = options + + if self.class::CACHEABLE_REQUESTS. + include?(name) and + cache = AWS.response_cache and + cached_response = cache.cached(response) + cached_response.cached = true + cached_response + else + # process the http request + options[:async] ? + make_async_request(response) : + make_sync_request(response) + + # process the http response + response.on_success do + send("process_#{name}_response", response) + if cache = AWS.response_cache + cache.add(response) + end + end + + response + + end + + end + + end + end + end + + private + def user_agent_string + engine = (RUBY_ENGINE rescue nil or "ruby") + user_agent = "%s aws-sdk-ruby/1.0 %s/%s %s" % + [config.user_agent_prefix, engine, RUBY_VERSION, RUBY_PLATFORM] + user_agent.strip! + if AWS.memoizing? + user_agent << " memoizing" + end + user_agent + end + + private + def self.add_client_request_method method_name, options = {}, &block + + method = ClientRequestMethodBuilder.new(self, method_name, &block) + + if xml_grammar = options[:xml_grammar] + + method.process_response do |resp| + xml_grammar.parse(resp.http_response.body, :context => resp) + super(resp) + end + + method.simulate_response do |resp| + xml_grammar.simulate(resp) + super(resp) + end + + end + + module_eval <<-END + def #{method_name}(*args, &block) + options = args.first ? args.first : {} + client_request(#{method_name.inspect}, options, &block) + end + END + + end + + # defined using MetaUtils so it can be extended by + # modules such as ConfiguredClientMethods + MetaUtils.extend_method(self, :configure_client) do + + module_eval('module Options; end') + module_eval('module XML; end') + + make_configurable :"#{service_ruby_name}_client", + :needs => [:signer, :http_handler] + + end + + # @private + class ClientRequestMethodBuilder + + def initialize client_class, method_name, &block + @client_class = client_class + @method_name = method_name + configure_request {|request, options|} + process_response {|response|} + simulate_response {|response|} + instance_eval(&block) + end + + def configure_request options = {}, &block + name = "configure_#{@method_name}_request" + MetaUtils.class_extend_method(@client_class, name, &block) + + if block.arity == 3 + m = Module.new + m.module_eval(<<-END) + def #{name}(req, options, &block) + super(req, options, block) + end + END + @client_class.send(:include, m) + end + end + + def process_response &block + name = "process_#{@method_name}_response" + MetaUtils.class_extend_method(@client_class, name, &block) + end + + def simulate_response &block + name = "simulate_#{@method_name}_response" + MetaUtils.class_extend_method(@client_class, name, &block) + end + + end + + end +end diff --git a/lib/aws/cacheable.rb b/lib/aws/cacheable.rb new file mode 100644 index 00000000000..2d153897ab7 --- /dev/null +++ b/lib/aws/cacheable.rb @@ -0,0 +1,92 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/naming' +require 'aws/meta_utils' + +module AWS + + # @private + module Cacheable + + def local_cache_key + raise NotImplementedError + end + + def cache_key + endpoint_method = self.class.service_ruby_name + "_endpoint" + config.signer.access_key_id + ":" + + config.send(endpoint_method) + ":" + + local_cache_key + end + + def attributes_from_response(response) + method = "populate_from_#{response.request_type}" + if respond_to?(method) + send(method, response) + end + end + + def request_types + self.class.request_types + end + + class NoData < StandardError; end + + def retrieve_attribute(attribute) + if cache = AWS.response_cache + if cache.resource_cache.cached?(cache_key, attribute) + return cache.resource_cache.get(cache_key, attribute) + end + cache.select(*request_types).each do |resp| + if attributes = attributes_from_response(resp) + cache.resource_cache.store(cache_key, attributes) + return attributes[attribute] if attributes.key?(attribute) + end + end + end + resp = yield + if attributes = attributes_from_response(resp) + if cache = AWS.response_cache + cache.resource_cache.store(cache_key, attributes) + end + attributes[attribute] if attributes.key?(attribute) + else + raise NoData.new("no data for #{self} in response to #{resp.request_type}") + end + end + + module ClassMethods + + def request_types + [] + end + + def populate_from(type, &block) + define_method("populate_from_#{type}", &block) + new_request_types = request_types + [type] + new_request_types.uniq! + MetaUtils.extend_method(self, :request_types) { new_request_types } + end + + end + + def self.included(mod) + mod.extend ClassMethods + mod.extend Naming unless + mod.respond_to?(:service_ruby_name) + end + + end + +end diff --git a/lib/aws/common.rb b/lib/aws/common.rb new file mode 100644 index 00000000000..f8d1ec86d91 --- /dev/null +++ b/lib/aws/common.rb @@ -0,0 +1,228 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/configuration' +require 'aws/resource_cache' +require 'aws/response_cache' + +# Root namespace for the AWS SDK for Ruby. +module AWS + class << self + + # @private + @@config = nil + + # The global configuration for AWS. Generally you set your prefered + # configuration operations once after loading the aws-sdk gem. + # + # AWS.config({ + # :access_key_id => 'ACCESS_KEY_ID', + # :secret_access_key => 'SECRET_ACCESS_KEY', + # :simple_db_endpoint => 'sdb.us-west-1.amazonaws.com', + # :max_retries => 2, + # }) + # + # When using AWS classes they will always default to use configuration + # values defined in {AWS.config}. + # + # AWS.config(:max_retries => 2) + # + # sqs = AWS::SQS.new + # sqs.config.max_retries #=> 2 + # + # If you want to change a configuration value for a single instance you + # pass the new configuration value to that objects initializer: + # + # AWS::SQS.new(:max_retries => 0) + # + # @note Changing the global configuration does not affect objects + # that have already been constructed. + # @param [Hash] options + # @option options [String] :access_key_id Your account access key + # id credential. + # @option options [String] :secret_access_key Your account secret + # access key credential. + # @option options [Integer] :max_retries (3) The maximum number of times + # service errors (500) should be retried. There is an exponential + # backoff in between service request retries, so the more retries the + # longer it can take to fail. + # @option options [String] :ec2_endpoint ('ec2.amazonaws.com') The + # service endpoint to use when communicating with Amazon EC2. + # @option options :http_handler The request/response handler + # for all service requests. The default handler uses HTTParty to + # send requests. + # @option options :logger (nil) A logger instance that should receive log + # messages generated by service requets. A logger needs to respond to + # #log and must accept a severity (e.g. :info, :error, etc) and + # a string message. + # @option options [String] :s3_endpoint ('s3.amazonaws.com') The + # service endpoint to use when communicating with Amazon S3. + # @option [Integer] :s3_multipart_threshold (16777216) When uploading + # data to S3, if the number of bytes to send exceedes + # +:s3_multipart_threshold+ then a multi part session is automatically + # started and the data is sent up in chunks. The size of each part + # is specified by +:s3_multipart_min_part_size+. + # @option [Integer] :s3_multipart_min_part_size (5242880) + # @option [Integer] :s3_multipart_max_parts (1000) + # @option options [Boolean] :simple_db_consistent_reads (false) When true + # all read operations against SimpleDB will be consistent reads (slower). + # @option options [String] :simple_db_endpoint ('sdb.amazonaws.com') The + # service endpoint to use when communicating with Amazon SimpleDB. + # @option options [String] :simple_email_service_endpoint ('email.us-east-1.amazonaws.com') + # The service endpoint to use when communicating with Amazon + # SimpleEmailService. + # @option options :signer The request signer. Defaults to a + # {DefaultSigner}. + # @option options [String] :sns_endpoint ('sns.us-east-1.amazonaws.com') + # The service endpoint to use when communicating with Amazon SNS. + # @option options [String] :sqs_endpoint ('sqs.us-east-1.amazonaws.com') + # The service endpoint to use when communicating with Amazon SQS. + # @option options :stub_requests (false) When true no requests will be + # made against the live service. Responses returned will have empty + # values. This is primarily used for writing tests. + # @option options [Boolean] :use_ssl (true) When true, all requests are + # sent over SSL. + # @option options [String] :user_agent_prefix (nil) A string prefix to + # append to all requets against AWS services. This should be set + # for clients and applications built ontop of the aws-sdk gem. + # @return [Configuration] Returns the new configuration. + def config options = {} + @@config ||= Configuration.new + @@config = @@config.with(options) unless options.empty? + @@config + end + + # @note Memoization is currently only supported for the EC2 APIs; + # other APIs are unaffected by the status of memoization. To + # protect your code from future changes in memoization support, + # you should not enable memoization while calling non-EC2 APIs. + # + # Starts memoizing service requests made in the current thread. + # See {memoize} for a full discussion of the memoization feature. + # This has no effect if memoization is already enabled. + def start_memoizing + Thread.current[:aws_memoization] ||= {} + nil + end + + # @note Memoization is currently only supported for the EC2 APIs; + # other APIs are unaffected by the status of memoization. To + # protect your code from future changes in memoization support, + # you should not enable memoization while calling non-EC2 APIs. + # + # Stops memoizing service requests made in the current thread. + # See {memoize} for a full discussion of the memoization feature. + # This has no effect if memoization is already disabled. + def stop_memoizing + Thread.current[:aws_memoization] = nil + end + + # @note Memoization is currently only supported for the EC2 APIs; + # other APIs are unaffected by the status of memoization. To + # protect your code from future changes in memoization support, + # you should not enable memoization while calling non-EC2 APIs. + # + # @return [Boolean] True if memoization is enabled for the current + # thread. See {memoize} for a full discussion of the + # memoization feature. + def memoizing? + !Thread.current[:aws_memoization].nil? + end + + # @note Memoization is currently only supported for the EC2 APIs; + # other APIs are unaffected by the status of memoization. To + # protect your code from future changes in memoization support, + # you should not enable memoization while calling non-EC2 APIs. + # + # Enables memoization for the current thread, within a block. + # Memoization lets you avoid making multiple requests for the same + # data by reusing the responses which have already been received. + # For example, consider the following code to get the most + # recently launched EC2 instance: + # + # latest = ec2.instances.sort_by(&:launch_time).last + # + # The above code would make N+1 requests (where N is the number of + # instances in the account); iterating the collection of instances + # is one request, and +Enumerable#sort_by+ calls + # {AWS::EC2::Instance#launch_time} for each instance, causing + # another request per instance. We can rewrite the code as + # follows to make only one request: + # + # latest = AWS.memoize do + # ec2.instances.sort_by(&:launch_time).last + # end + # + # Iterating the collection still causes a request, but each + # subsequent call to {AWS::EC2::Instance#launch_time} uses the + # results from that first request rather than making a new request + # for the same data. + # + # While memoization is enabled, every response that is received + # from the service is retained in memory. Therefore you should + # use memoization only for short-lived blocks of code that make + # relatively small numbers of requests. The cached responses are + # used in two ways while memoization is enabled: + # + # 1. Before making a request, the SDK checks the cache for a + # response to a request with the same signature (credentials, + # service endpoint, operation name, and parameters). If such a + # response is found, it is used instead of making a new + # request. + # + # 2. Before retrieving data for an attribute of a resource + # (e.g. {AWS::EC2::Instance#launch_time}), the SDK attempts to + # find a cached response that contains the requested data. If + # such a response is found, the cached data is returned instead + # of making a new request. + # + # When memoization is disabled, all previously cached responses + # are discarded. + def memoize + return yield if memoizing? + + begin + start_memoizing + yield if block_given? + ensure + stop_memoizing + end + end + + # @private + def resource_cache + if memoizing? + Thread.current[:aws_memoization][:resource_cache] ||= + ResourceCache.new + end + end + + # @private + def response_cache + if memoizing? + Thread.current[:aws_memoization][:response_cache] ||= + ResponseCache.new + end + end + + # Causes all requests to return empty responses without making any + # requests against the live services. This does not attempt to + # mock the services. + # @return [nil] + def stub! + config(:stub_requests => true) + nil + end + + end +end diff --git a/lib/aws/configurable.rb b/lib/aws/configurable.rb new file mode 100644 index 00000000000..03190447b81 --- /dev/null +++ b/lib/aws/configurable.rb @@ -0,0 +1,36 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/configuration' + +module AWS + + # @private + module Configurable + + module ClassMethods + + def make_configurable(name, opts = {}) + Configuration.add_parameter_type(self, name, opts) + end + + end + + def self.included(m) + m.extend ClassMethods + super + end + + end + +end diff --git a/lib/aws/configuration.rb b/lib/aws/configuration.rb new file mode 100644 index 00000000000..d3ada1be84d --- /dev/null +++ b/lib/aws/configuration.rb @@ -0,0 +1,272 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/default_signer' +require 'aws/http/httparty_handler' +require 'set' + +module AWS + + # A configuration object for AWS interfaces and clients. + # + # == Configuring Credential + # + # In order to do anything with AWS you will need to assign credentials. + # The simplest method is to assing your credentials into the default + # configuration: + # + # AWS.config(:access_key_id => 'KEY', :secret_access_key => 'SECRET') + # + # You can also export them into your environment and they will be picked up + # automatically: + # + # export AWS_ACCESS_KEY_ID='YOUR_KEY_ID_HERE' + # export AWS_SECRET_ACCESS_KEY='YOUR_SECRET_KEY_HERE' + # + # For compatability with other AWS gems, the credentials can also be + # exported like: + # + # export AMAZON_ACCESS_KEY_ID='YOUR_KEY_ID_HERE' + # export AMAZON_SECRET_ACCESS_KEY='YOUR_SECRET_KEY_HERE' + # + # == Modifying a Configuration + # + # Configuration objects are read-only. If you need a different set of + # configuration values, call {#with}, passing in the updates + # and a new configuration object will be returned. + # + # config = Configuration.new(:max_retires => 3) + # new_config = config.with(:max_retries => 2) + # + # config.max_retries #=> 3 + # new_config.max_retries #=> 2 + # + # == Global Configuration + # + # The global default configuration can be found at {AWS.config} + # + class Configuration + + # Creates a new Configuration object. + # @param options (see AWS.config) + # @option options (see AWS.config) + def initialize options = {} + + @create_options = options.delete(:__create_options__) || {} + + @overridden = options.delete(:__overridden__) || + Set.new(options.keys.map { |k| k.to_sym }) + + @options = { + :ec2_endpoint => 'ec2.amazonaws.com', + :http_handler => Http::HTTPartyHandler.new, + :max_retries => 3, + :s3_endpoint => 's3.amazonaws.com', + :s3_multipart_threshold => 16 * 1024 * 1024, + :s3_multipart_min_part_size => 5 * 1024 * 1024, + :s3_multipart_max_parts => 10000, + :simple_db_endpoint => 'sdb.amazonaws.com', + :simple_db_consistent_reads => false, + :simple_email_service_endpoint => 'email.us-east-1.amazonaws.com', + :sns_endpoint => 'sns.us-east-1.amazonaws.com', + :sqs_endpoint => 'sqs.us-east-1.amazonaws.com', + :stub_requests => false, + :use_ssl => true, + :user_agent_prefix => nil, + } + + { + 'AWS_ACCESS_KEY_ID' => :access_key_id, + 'AWS_SECRET_ACCESS_KEY' => :secret_access_key, + 'AMAZON_ACCESS_KEY_ID' => :access_key_id, + 'AMAZON_SECRET_ACCESS_KEY' => :secret_access_key, + }.each_pair do |env_key, opt_key| + if ENV[env_key] + @options[opt_key] = ENV[env_key] + end + end + + options.each do |(k,v)| + @options[k.to_sym] = v + end + + end + + # @return [Boolean] Returns true if web service requets should be + # made with HTTPS. + def use_ssl? + @options[:use_ssl] + end + alias_method :use_ssl, :use_ssl? + + # @return [String] Your AWS account access key id credential. + def access_key_id + @options[:access_key_id] + end + + # @return [String] Your AWS secret access key credential. + def secret_access_key + @options[:secret_access_key] + end + + # Used to create a new Configuration object with the given modifications. + # The current configuration object is not modified. + # + # AWS.config(:max_retries => 2) + # + # no_retries_config = AWS.config.with(:max_retries => 0) + # + # AWS.config.max_retries #=> 2 + # no_retries_config.max_retries #=> 0 + # + # You can use these configuration objects returned by #with to create + # AWS objects: + # + # AWS::S3.new(:config => no_retries_config) + # AWS::SQS.new(:config => no_retries_config) + # + # @param options (see AWS.config) + # @option options (see AWS.config) + # @return [Configuration] Copies the current configuration and returns + # a new one with modifications as provided in +:options+. + def with options = {} + overridden = @overridden + options.keys.map { |k| k.to_sym } + self.class.new(@options.merge(options). + merge(:__create_options__ => @create_options, + :__overridden__ => overridden)) + end + + # @return [Integer] Maximum number of times to retry server errors. + def max_retries + @options[:max_retries] + end + + # @return [String] The service endpoint for Amazon S3. + def s3_endpoint + @options[:s3_endpoint] + end + + # @return [String] The default service endpoint for Amazon EC2. + def ec2_endpoint + @options[:ec2_endpoint] + end + + # @return [String] The service endpoint for Amazon SimpleDB. + def simple_db_endpoint + @options[:simple_db_endpoint] + end + + # @return [String] The service endpoint for Amazon SimpleEmailService. + def simple_email_service_endpoint + @options[:simple_email_service_endpoint] + end + + # @return [String] The service endpoint for Amazon SNS. + def sns_endpoint + @options[:sns_endpoint] + end + + # @return [String] The service endpoint for Amazon SQS. + def sqs_endpoint + @options[:sqs_endpoint] + end + + # @return [Boolean] Returns true if all reads to SimpleDB default to + # consistent reads. + def simple_db_consistent_reads? + @options[:simple_db_consistent_reads] + end + + # @return [String,nil] Returns the prefix that is appended to the + # user agent string that is sent with all requests to AWS. + def user_agent_prefix + @options[:user_agent_prefix] + end + + # @return [Object] Returns the current http handler. + def http_handler + @options[:http_handler] + end + + # @return [Object] Returns the current request signer. + def signer + return @options[:signer] if @options[:signer] + raise "Missing credentials" unless access_key_id and secret_access_key + @options[:signer] ||= DefaultSigner.new(access_key_id, secret_access_key) + end + + # @return [Object,nil] Returns the current logger. + def logger + @options[:logger] + end + + # @return [Boolean] Returns true if this configuration causes + # all AWS requests to return stubbed (empty) responses without making a + # request to the actual service. + def stub_requests? + @options[:stub_requests] + end + + # @return [Integer] Returns the number of bytes where files larger + # are uploaded to S3 in multiple parts. + def s3_multipart_threshold + @options[:s3_multipart_threshold] + end + + # @return [Integer] The absolute minimum size (in bytes) each S3 multipart + # segment should be. + def s3_multipart_min_part_size + @options[:s3_multipart_min_part_size] + end + + # @return [Integer] The maximum number of parts to split a file into + # when uploading to S3. + def s3_multipart_max_parts + @options[:s3_multipart_max_parts] + end + + # @private + def inspect + "<#{self.class}>" + end + + class << self + + # @private + def add_parameter_type(klass, name, options = {}) + if options[:needs] + define_method(name) do + unless @overridden.include?(name) + create_options = { :config => self } + options[:needs].each do |need| + create_options[need] = send(need) + end + end + + return @options[name] if @options[name] and + (@overridden.include?(name) or + @create_options[name] == create_options) + + @create_options[name] = create_options + @options[name] = klass.new(create_options) + end + else + define_method(name) do + @options[name] ||= klass.new(:config => self) + end + end + end + + end + end +end diff --git a/lib/aws/configured_client_methods.rb b/lib/aws/configured_client_methods.rb new file mode 100644 index 00000000000..e6356fdca9e --- /dev/null +++ b/lib/aws/configured_client_methods.rb @@ -0,0 +1,81 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/api_config' +require 'aws/inflection' +require 'aws/configured_xml_grammars' +require 'aws/configured_option_grammars' + +module AWS + + # @private + module ConfiguredClientMethods + + # @private + module ClassMethods + + include ApiConfig + + def configure_client + + super + + unless self::XML.include?(ConfiguredXmlGrammars) + self::XML.module_eval do + include(ConfiguredXmlGrammars) + define_configured_grammars + end + end + + unless self::Options.include?(ConfiguredOptionGrammars) + self::Options.module_eval do + include(ConfiguredOptionGrammars) + define_configured_grammars + end + end + + api_config[:operations].each do |name, customizations| + option_grammar = self::Options.operation_grammar(name) + add_client_request_method(Inflection.ruby_name(name).to_sym, + :xml_grammar => + self::XML.operation_grammar(name)) do + configure_request do |request, options| + request.add_param("Action", name) + option_grammar.request_params(options).each do |param| + request.add_param(param) + end + end + end + end + + end + + def operation_xml_grammar(name) + customized_name = "Customized#{name}" + if self::XML.const_defined?(customized_name) + self::XML.const_get(customized_name) + else + self::XML.const_get(name) + end + end + + end + + def self.included(mod) + mod.extend(ClassMethods) + end + + end + +end + diff --git a/lib/aws/configured_grammars.rb b/lib/aws/configured_grammars.rb new file mode 100644 index 00000000000..063b3f4d441 --- /dev/null +++ b/lib/aws/configured_grammars.rb @@ -0,0 +1,65 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/api_config' +require 'aws/xml_grammar' + +module AWS + + # @private + module ConfiguredGrammars + + # @private + module ClassMethods + + include ApiConfig + + def base_grammar + raise NotImplementedError + end + + def operation_grammar(name) + customized_name = "Customized#{name}" + if const_defined?(customized_name) + const_get(customized_name) + else + const_get(name) + end + end + + def input_or_output + raise NotImplementedError + end + + def process_customizations(name, customizations) + customizations + end + + def define_configured_grammars + api_config[:operations].each do |name, data| + customizations = process_customizations(name, + data[input_or_output]) + const_set(name, + base_grammar.customize(customizations)) + # BaseResponse.customize([{ + # "#{name}Result" => + # [:ignore, *data[:output]] + # }])) + end + end + + end + + end + +end diff --git a/lib/aws/configured_option_grammars.rb b/lib/aws/configured_option_grammars.rb new file mode 100644 index 00000000000..43e860fb421 --- /dev/null +++ b/lib/aws/configured_option_grammars.rb @@ -0,0 +1,46 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/configured_grammars' +require 'aws/option_grammar' + +module AWS + + # @private + module ConfiguredOptionGrammars + + module ClassMethods + + include ConfiguredGrammars::ClassMethods + + def base_grammar + if const_defined?(:BaseOptions) + const_get(:BaseOptions) + else + OptionGrammar + end + end + + def input_or_output + :input + end + + end + + def self.included(mod) + mod.extend(ClassMethods) + end + + end + +end diff --git a/lib/aws/configured_xml_grammars.rb b/lib/aws/configured_xml_grammars.rb new file mode 100644 index 00000000000..39ce52bdb5f --- /dev/null +++ b/lib/aws/configured_xml_grammars.rb @@ -0,0 +1,47 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/configured_grammars' +require 'aws/xml_grammar' + +module AWS + + # @private + module ConfiguredXmlGrammars + + # @private + module ClassMethods + + include ConfiguredGrammars::ClassMethods + + def base_grammar + if const_defined?(:BaseResponse) + const_get(:BaseResponse) + else + XmlGrammar + end + end + + def input_or_output + :output + end + + end + + def self.included(mod) + mod.extend(ClassMethods) + end + + end + +end diff --git a/lib/aws/default_signer.rb b/lib/aws/default_signer.rb new file mode 100644 index 00000000000..742a7699720 --- /dev/null +++ b/lib/aws/default_signer.rb @@ -0,0 +1,38 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'base64' +require 'openssl' + +module AWS + + # @private + class DefaultSigner + + attr_reader :access_key_id, :secret_access_key + + def initialize(access_key_id, secret_access_key) + @access_key_id = access_key_id + @secret_access_key = secret_access_key + end + + def sign(string_to_sign, digest_method = 'sha256') + Base64.encode64( + OpenSSL::HMAC.digest( + OpenSSL::Digest::Digest.new(digest_method), + secret_access_key, + string_to_sign)).strip + end + + end +end diff --git a/lib/aws/ec2.rb b/lib/aws/ec2.rb new file mode 100644 index 00000000000..898655a2fe3 --- /dev/null +++ b/lib/aws/ec2.rb @@ -0,0 +1,321 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/common' +require 'aws/service_interface' +require 'aws/ec2/client' +require 'aws/ec2/instance_collection' +require 'aws/ec2/security_group_collection' +require 'aws/ec2/elastic_ip_collection' +require 'aws/ec2/key_pair_collection' +require 'aws/ec2/tag_collection' +require 'aws/ec2/region_collection' +require 'aws/ec2/availability_zone_collection' +require 'aws/ec2/image_collection' +require 'aws/ec2/volume_collection' +require 'aws/ec2/snapshot_collection' +require 'aws/ec2/reserved_instances_collection' +require 'aws/ec2/reserved_instances_offering_collection' + +module AWS + + # Provides an expressive, object-oriented interface to Amazon EC2. + # + # == Credentials + # + # You can setup default credentials for all AWS services via + # AWS.config: + # + # AWS.config( + # :access_key_id => 'YOUR_ACCESS_KEY_ID', + # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') + # + # Or you can set them directly on the EC2 interface: + # + # ec2 = AWS::EC2.new( + # :access_key_id => 'YOUR_ACCESS_KEY_ID', + # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') + # + # == Instances + # + # EC2 uses instances to run your software. + # + # To run an instance: + # + # ec2.instances.create(:image_id => "ami-8c1fece5") + # + # To get an instance by ID: + # + # i = ec2.instances["i-12345678"] + # i.exists? + # + # To get a list of instances: + # + # ec2.instances.inject({}) { |m, i| m[i.id] = i.status; m } + # # => { "i-12345678" => :running, "i-87654321" => :shutting_down } + # + # == Security Groups + # + # A security group is a named collection of access rules. These access + # rules specify which ingress (i.e., incoming) network traffic should be + # delivered to your instance. All other ingress traffic will be discarded. + # + # To create a security group: + # + # websvr = ec2.security_groups.create('webservers') + # + # Then you can add ingress authorizations. In the following example + # we add a rule that allows web traffic from the entire internet. + # + # # web traffic + # websvr.authorize_ingress(:tcp, 80) + # + # You can also specify a port range. Here we are opening FTP traffic: + # + # # ftp traffic + # websvr.authorize_ingress(:tcp, 20..21) + # + # If you want to limit an authorization to a particular CIDR IP address or + # list of address, just add them to the #authorize_ingress call. + # + # # ssh access + # websrvr.authorize_ingress(:tcp, 22, '1.1.1.1/0', '2.2.2.2/0') + # + # You can also provide another security group instead of CIDR IP addresses. + # This allows incoming traffic from EC2 instances in the given security + # group(s). + # + # # get two existing security groups + # dbsvrs = ec2.security_groups['db-servers'] + # websvrs = ec2.security_groups['web-servers'] + # + # # allow instances in the 'web-servers' security group to connect + # # to instances in the 'db-servers' security group over tcp port 3306 + # dbsvrs.authorize_ingress(:tcp, 3306, websvrs) + # + # There are a few handy shortcuts for allowing pings: + # + # wbsvrs.allow_ping + # + # Just like with authorize_ingress you can pass a security group or a list + # of CIDR IP addresses to allow ping to limit where you can ping from. + # + # You can also use the same parameters from the examples above to + # {SecurityGroup#revoke_ingress} and {SecurityGroup#disallow_ping}. + # + # You can specify other protocols than +:tcp+, like :udp and :icmp. + # + # == Elastic IPs + # + # You can allocate up to 5 elastic IP addresses for each account. + # You can associate those elastic IP addresses with EC2 instances: + # + # instance = ec2.instances['i-12345678'] + # ip = ec2.elastic_ips.allocate + # + # instance.ip_address # 1.1.1.1 + # ip.ip_address # 2.2.2.2 + # + # instance.associate_elastic_ip(ip) + # instance.ip_address # 2.2.2.2 + # + # instance.disassociate_elastic_ip + # instance.ip_address # 1.1.1.1 + # + # When you are done with an elastic IP address you should release it. + # In the following example we release all elastic IP addresses that are + # not currently associated with an instance: + # + # ec2.select{|ip| !ip.associated? }.each(&:release) + # + # == Key Pairs + # + # Public Amazon Machine Image (AMI) instances have no password, and you need a + # public/private key pair to log in to them. The public key half + # of this pair is embedded in your instance, allowing you to use + # the private key to log in securely without a password. + # + # You can generate a key pair yourself and then send the public + # part to EC2 using {KeyPairCollection#import}. For example: + # + # key_pair = + # ec2.key_pairs.import("mykey", File.read("~/.ssh/identity.pub")) + # + # You can also ask EC2 to generate a key pair for you. For + # example: + # + # key_pair = ec2.key_pairs.create("mykey") + # File.open("~/.ssh/ec2", "w") do |f| + # f.write(key_pair.private_key) + # end + # + # == Filtering and Tagging + # + # Any of the collections in the interface may be filtered by a + # number of different parameters. For example, to get all the + # windows images owned by amazon where the description includes + # the string "linux", you can do this: + # + # ec2.images.with_owner("amazon"). + # filtered("platform", "windows"). + # filtered("description", "*linux*") + # + # Similarly, you can tag images, instances, security groups, + # snapshots, and volumes with free-form key-value metadata and + # filter on that metadata. For example: + # + # ec2.images["ami-123"].tags << "myapp" + # ec2.images.tagged("myapp") # will include ami-123 + # + # == Regions + # + # Amazon has data centers in different areas of the world (e.g., + # North America, Europe, Asia, etc.). Correspondingly, EC2 is + # available to use in different Regions. By launching instances in + # separate Regions, you can design your application to be closer + # to specific customers or to meet legal or other + # requirements. Prices for Amazon EC2 usage vary by Region (for + # more information about pricing by Region, go to the {Amazon EC2 + # Pricing page}[http://aws.amazon.com/ec2/pricing]). You can use + # the Ruby SDK to see which regions are available for your + # account: + # + # ec2.regions.map(&:name) # => ["us-east-1", ...] + # + # The default region is +us-east-1+; you can access other regions + # like this: + # + # ec2_us_west = ec2.regions["us-west-1"] + # # starts an instance in eu-west-1 + # ec2_us_west.instances.create(:image_id => 'ami-3bc9997e') + # + # This makes a call to EC2's DescribeRegions API to find the + # endpoint for "us-west-1" -- if you just want to configure a + # different endpoint without making a call to EC2, you can do it + # like this: + # + # ec2 = AWS::EC2.new(:ec2_endpoint => + # "ec2.us-west-1.amazonaws.com") + # + # == Availability Zones + # + # Each Region contains multiple distinct locations called + # Availability Zones. Each Availability Zone is engineered to be + # isolated from failures in other Availability zones and to + # provide inexpensive, low-latency network connectivity to other + # zones in the same Region. By launching instances in separate + # Availability Zones, you can protect your applications from the + # failure of a single location. + # + # You can use the {#availability_zones} collection to get information + # about the available zones available to your account. For + # example: + # + # ec2.availability_zones.map(&:name) # => ["us-east-1a", ...] + # + # == Images + # + # An Amazon Machine Image (AMI) contains all information necessary + # to boot instances of your software. For example, an AMI might + # contain all the software to act as a web server (e.g., Linux, + # Apache, and your web site) or it might contain all the software + # to act as a Hadoop node (e.g., Linux, Hadoop, and a custom + # application). + # + # You can use the {#images} collection to get information about + # the images available to your account. For example: + # + # ec2.images.with_owner("amazon").map(&:name) + # + # You can also use the images collection to create new images: + # + # ec2.images.create(:image_location => "mybucket/manifest.xml", + # :name => "my-image") + # + class EC2 + + include ServiceInterface + + # @return [InstanceCollection] A collection representing all instances + def instances + InstanceCollection.new(:config => config) + end + + # @return [SecurityGroupCollection] A collection representing all security + # groups. + def security_groups + SecurityGroupCollection.new(:config => config) + end + + # @return [ElasticIpCollection] A collection representing all + # elastic IP addresses for this account. + def elastic_ips + ElasticIpCollection.new(:config => config) + end + + # @return [KeyPairCollection] A collection representing all key pairs. + def key_pairs + KeyPairCollection.new(:config => config) + end + + # @return [TagCollection] A collection representing all EC2 tags for + # all resource types. + def tags + TagCollection.new(:config => config) + end + + # @return [RegionCollection] A collection representing all EC2 + # regions. + def regions + RegionCollection.new(:config => config) + end + + # @return [AvailabilityZoneCollection] A collection representing + # all EC2 availability zones. + def availability_zones + AvailabilityZoneCollection.new(:config => config) + end + + # @return [ImageCollection] A collection representing + # all Amazon Machine Images available to your account. + def images + ImageCollection.new(:config => config) + end + + # @return [VolumeCollection] A collection representing + # all EBS volumes available to your account. + def volumes + VolumeCollection.new(:config => config) + end + + # @return [ReservedInstancesCollection] A collection representing all + # purchased reserved instance offerings. + def reserved_instances + ReservedInstancesCollection.new(:config => config) + end + + # @return [ReservedInstancesOfferingCollection] A collection representing all + # reserved instance offerings that may be purchased. + def reserved_instances_offerings + ReservedInstancesOfferingCollection.new(:config => config) + end + + # @return [SnapshotCollection] A collection representing + # all EBS snapshots available to your account. + def snapshots + SnapshotCollection.new(:config => config) + end + + end + +end diff --git a/lib/aws/ec2/attachment.rb b/lib/aws/ec2/attachment.rb new file mode 100644 index 00000000000..302811f9cf5 --- /dev/null +++ b/lib/aws/ec2/attachment.rb @@ -0,0 +1,149 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/cacheable' + +module AWS + class EC2 + + # Represents an attachment of an Amazon EBS volume to an instance. + # + # @example Create an empty 15GiB volume and attach it to an instance + # volume = ec2.volumes.create(:size => 15, + # :availability_zone => "us-east-1a") + # attachment = volume.attach_to(ec2.instances["i-123"], "/dev/sdf") + # sleep 1 until attachment.status != :attaching + # + # @example Remove all attachments from a volume and then delete it + # volume.attachments.each do |attachment| + # attachment.delete(:force => true) + # end + # sleep 1 until volume.status == :available + # volume.delete + class Attachment + + include Model + include Cacheable + + attr_reader :volume + attr_reader :instance + attr_reader :device + + # @private + def initialize(volume, instance, device, opts = {}) + @volume = volume + @instance = instance + @device = device + super(opts) + end + + # (see Volume#detach_from) + def delete(opts = {}) + client.detach_volume(opts.merge(:volume_id => volume.id, + :instance_id => instance.id, + :device => device)) + end + + # @return [Symbol] The attachment status. Possible values: + # * +:attaching+ + # * +:attached+ + # * +:detaching+ + # * +:detached+ + def status + retrieve_attribute(:status) { describe_call } + end + + # @return [Time] The time at which this attachment was created. + def attach_time + retrieve_attribute(:attach_time) { describe_call } + end + + # @return [Boolean] True if the volume will be deleted on + # instance termination. + def delete_on_termination? + retrieve_attribute(:delete_on_termination?) { describe_call } + end + + # @return [Boolean] True if the attachment exists. + def exists? + !describe_attachment.nil? + end + + # @return [Boolean] True if the attachments are the same. + # + # @param [Object] other The object to compare with. + def ==(other) + other.kind_of?(Attachment) and + other.volume == volume and + other.instance == instance and + other.device == device + end + alias_method :eql?, :== + + populate_from :describe_volumes do |resp| + if volume = resp.volume_index[self.volume.id] and + attachments = volume.attachment_set and + attachment = attachments.find do |att| + att.instance_id == self.instance.id && + att.volume_id == self.volume.id && + att.device == self.device + end + + attributes_from_response_object(attachment) + end + end + + [:attach_volume, + :detach_volume].each do |op| + populate_from op do |resp| + attributes_from_response_object(resp) if + resp.volume_id == volume.id and + resp.instance_id == instance.id and + resp.device == device + end + end + + protected + def attributes_from_response_object(attachment) + atts = {} + atts[:status] = (attachment.status.to_sym if + attachment.respond_to?(:status)) + atts[:attach_time] = (attachment.attach_time if + attachment.respond_to?(:attach_time)) + atts[:delete_on_termination?] = (attachment.delete_on_termination? if + attachment.respond_to?(:delete_on_termination?)) + atts + end + + protected + def describe_call + client.describe_volumes(:volume_ids => [self.volume.id]) + end + + private + def describe_attachment + (resp = describe_call and + volume = resp.volume_index[self.volume.id] and + attachments = volume.attachment_set and + attachment = attachments.find do |att| + att.instance_id == self.instance.id && + att.volume_id == self.volume.id && + att.device == self.device + end) or nil + end + + end + + end +end diff --git a/lib/aws/ec2/attachment_collection.rb b/lib/aws/ec2/attachment_collection.rb new file mode 100644 index 00000000000..65b309d36b4 --- /dev/null +++ b/lib/aws/ec2/attachment_collection.rb @@ -0,0 +1,57 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/ec2/volume' +require 'aws/ec2/instance' +require 'aws/ec2/attachment' + +module AWS + class EC2 + + # Represents the collection of attachments for an Amazon EBS + # volume. + # + # @see Volume + class AttachmentCollection + + include Model + include Enumerable + + attr_reader :volume + + # @private + def initialize(volume, opts = {}) + @volume = volume + super(opts) + end + + # @yield [attachment] Each attachment of the volume as an + # {Attachment} object. + # @return [nil] + def each(&block) + volume.attachment_set.each do |att| + instance = Instance.new(att.instance_id, :config => config) + attachment = Attachment.new(self.volume, + instance, + att.device, + :config => config) + yield(attachment) + end + nil + end + + end + + end +end diff --git a/lib/aws/ec2/availability_zone.rb b/lib/aws/ec2/availability_zone.rb new file mode 100644 index 00000000000..765309acea8 --- /dev/null +++ b/lib/aws/ec2/availability_zone.rb @@ -0,0 +1,80 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/resource' + +module AWS + class EC2 + + # Represents an EC2 availability zone. You can use this class + # to get information about the state of an availability zone + # that is available to your account. + class AvailabilityZone < Resource + + # @return [String] Returns the name of the availability zone, + # e.g. "us-east-1a". + attr_reader :name + + alias_method :to_s, :name + alias_method :to_str, :name + + # @param [String] name The name of the availability zone. + def initialize(name, opts = {}) + @name = name + @region = opts[:region] + super(opts) + end + + # @return [Region] The region of this availability zone. + def region; end + describe_call_attribute :region_name, :getter => :region, :memoize => true do + translate_output do |value| + Region.new(value, :config => config) if value + end + end + + # @return [Symbol] The state of the availability zone, + # e.g. +:available+. + def state; end + describe_call_attribute :zone_state, :getter => :state, :to_sym => true + + # @return [Array] A list of messages about the Availability + # Zone. + def messages; end + describe_call_attribute :message_set, :getter => :messages do + translate_output { |set| set.map { |m| m.message } if set } + end + + protected + def inflected_name + "zone" + end + + protected + def find_in_response(resp) + resp.availability_zone_info.find { |az| az.zone_name == name } + end + + # @private + private + def get_attribute(name) + resp = client.describe_availability_zones(:zone_names => + [self.name]) + az = resp.availability_zone_info.first + az.send(name) + end + + end + + end +end diff --git a/lib/aws/ec2/availability_zone_collection.rb b/lib/aws/ec2/availability_zone_collection.rb new file mode 100644 index 00000000000..0c90bdfede2 --- /dev/null +++ b/lib/aws/ec2/availability_zone_collection.rb @@ -0,0 +1,47 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/collection' +require 'aws/ec2/availability_zone' + +module AWS + class EC2 + + # Represents all EC2 availability zones that are currently + # available to your account. + class AvailabilityZoneCollection < Collection + + # Yields each of the EC2 availability zones. + # @return [nil] + def each &block + resp = filtered_request(:describe_availability_zones) + resp.availability_zone_info.each do |az| + zone = AvailabilityZone.new(az.zone_name, + :region => Region.new(az.region_name, :config => config), + :config => config + ) + yield(zone) + end + nil + end + + # @private + protected + def member_class + AvailabilityZone + end + + end + + end +end diff --git a/lib/aws/ec2/block_device_mappings.rb b/lib/aws/ec2/block_device_mappings.rb new file mode 100644 index 00000000000..7cfa30a4d8e --- /dev/null +++ b/lib/aws/ec2/block_device_mappings.rb @@ -0,0 +1,53 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + class EC2 + + # @private + module BlockDeviceMappings + + # @private + private + def translate_block_device_mappings(mapping) + raise ArgumentError.new("block_device_mappings must be a hash") unless + mapping.kind_of?(Hash) + mapping.map do |device, dest| + raise ArgumentError.new("keys of block_device_mappings must be strings") unless + device.kind_of?(String) + entry = { :device_name => device } + case dest + when :no_device + # for some reason EC2 rejects boolean values for this seemingly boolean option + entry[:no_device] = "" + when Symbol + raise ArgumentError.new("unrecognized block device mapping: #{dest}") + when String + entry[:virtual_name] = dest + when Hash + if snapshot = dest.delete(:snapshot) + dest[:snapshot_id] = snapshot.id + end + entry[:ebs] = dest + else + raise ArgumentError.new("values of block_device_mappings must "+ + "be strings, symbols, or hashes") + end + entry + end + end + + end + + end +end diff --git a/lib/aws/ec2/client.rb b/lib/aws/ec2/client.rb new file mode 100644 index 00000000000..ab8c9731ba3 --- /dev/null +++ b/lib/aws/ec2/client.rb @@ -0,0 +1,54 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/base_client' +require 'aws/configured_client_methods' +require 'aws/ec2/request' +require 'aws/ec2/client/xml' +require 'aws/ec2/errors' +require 'aws/inflection' + +module AWS + class EC2 + + # @private + class Client < BaseClient + + include ConfiguredClientMethods + + API_VERSION = '2011-02-28' + + REQUEST_CLASS = EC2::Request + + # @private + CACHEABLE_REQUESTS = Set[:describe_instances, + :describe_instance_attribute, + :describe_images, + :describe_image_attribute, + :describe_volumes, + :describe_security_groups, + :describe_addresses, + :describe_key_pairs, + :describe_regions, + :describe_availability_zones, + :describe_reserved_instances, + :describe_reserved_instances_offerings, + :describe_snapshots, + :describe_snapshot_attribute, + :describe_tags] + + configure_client + + end + end +end diff --git a/lib/aws/ec2/client/xml.rb b/lib/aws/ec2/client/xml.rb new file mode 100644 index 00000000000..4e211b78e0d --- /dev/null +++ b/lib/aws/ec2/client/xml.rb @@ -0,0 +1,127 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/configured_xml_grammars' + +module AWS + class EC2 + class Client < BaseClient + module XML + + include ConfiguredXmlGrammars + + BaseError = XmlGrammar.customize do + element "Errors" do + ignore + element("Error") { ignore } + end + end + + define_configured_grammars + + CustomizedDescribeSecurityGroups = DescribeSecurityGroups.customize do + element "securityGroupInfo" do + element "item" do + index(:security_group_index) { |i| i.group_id } + + element "ipPermissions" do + list "item" + element "item" do + element("ipProtocol") { symbol_value } + element("fromPort") { integer_value } + element("toPort") { integer_value } + element("groups") { list "item" } + element("ipRanges") { list "item" } + end + end + end + end + end + + CustomizedDescribeInstances = DescribeInstances.customize do + element "reservationSet" do + element "item" do + index :reservation_index do |r| + r.instances_set.map { |i| i.instance_id } + end + + element "instancesSet" do + element "item" do + index(:instance_index) { |i| i.instance_id } + end + end + end + end + end + + CustomizedDescribeImages = DescribeImages.customize do + element "imagesSet" do + element "item" do + index(:image_index) { |i| i.image_id } + end + end + end + + CustomizedDescribeVolumes = DescribeVolumes.customize do + element "volumeSet" do + element "item" do + index(:volume_index) { |v| v.volume_id } + end + end + end + + CustomizedDescribeSnapshots = DescribeSnapshots.customize do + element "snapshotSet" do + element "item" do + index(:snapshot_index) { |s| s.snapshot_id } + end + end + end + + CustomizedDescribeAddresses = DescribeAddresses.customize do + element "addressesSet" do + element "item" do + index(:address_index) { |a| a.public_ip } + element "instanceId" do + force + end + end + end + end + + CustomizedDescribeKeyPairs = DescribeKeyPairs.customize do + element "keySet" do + element "item" do + index(:key_index) { |k| k.key_name } + end + end + end + + CustomizedDescribeTags = DescribeTags.customize do + element "tagSet" do + element "item" do + element("resourceType") { force } + element("resourceId") { force } + element("key") { force } + index(:tag_index) do |t| + "#{t.resource_type}:#{t.resource_id}:#{t.key}" + end + end + end + end + + end + + end + end +end diff --git a/lib/aws/ec2/collection.rb b/lib/aws/ec2/collection.rb new file mode 100644 index 00000000000..1c2ac1e3fb5 --- /dev/null +++ b/lib/aws/ec2/collection.rb @@ -0,0 +1,39 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/ec2/filtered_collection' + +module AWS + class EC2 + + # @private + class Collection + + include Model + include Enumerable + include FilteredCollection + + def [] id + member_class.new(id.to_s, :config => config) + end + + protected + def member_class + raise NotImplementedError + end + + end + + end +end diff --git a/lib/aws/ec2/config_transform.rb b/lib/aws/ec2/config_transform.rb new file mode 100644 index 00000000000..44168663455 --- /dev/null +++ b/lib/aws/ec2/config_transform.rb @@ -0,0 +1,63 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +module AWS + class EC2 + + # @private + class ConfigTransform + + class << self + + def transform(api_config) + ["RunInstances", + "DetachVolume", + "AttachVolume", + "CreateSnapshot", + "CreateVolume", + "DescribeImageAttribute", + "DescribeInstanceAttribute"].each do |(op, wrapper)| + if op_config = api_config[:operations][op] + op_config[:output] = op_config[:output].first.values.first + end + end + + api_config[:operations].each do |op, op_config| + op_config[:input] = + capitalize_input_structure(op_config[:input]) + end + + api_config + end + + private + def capitalize_input_structure(struct) + struct.inject({}) do |m, (name, desc)| + + name = name[0,1].upcase + name[1..-1] + m[name] = capitalize_input_descriptors(desc) + m + end + end + + private + def capitalize_input_descriptors(desc) + desc.map do |d| + case + when d.kind_of?(Hash) && d.key?(:structure) + d.merge(:structure => + capitalize_input_structure(d[:structure])) + when d.kind_of?(Hash) && d.key?(:list) + d.merge(:list => + capitalize_input_descriptors(d[:list])) + else + d + end + end + end + + end + + end + + end +end diff --git a/lib/aws/ec2/elastic_ip.rb b/lib/aws/ec2/elastic_ip.rb new file mode 100644 index 00000000000..68ea250ab96 --- /dev/null +++ b/lib/aws/ec2/elastic_ip.rb @@ -0,0 +1,107 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/resource' + +module AWS + class EC2 + class ElasticIp < Resource + + def initialize public_ip, options = {} + @public_ip = public_ip + @instance_id = options[:instance_id] + super + end + + # @private + def self.describe_call_name; :describe_addresses; end + + # @private + def describe_call_name; self.class.describe_call_name; end + + # @return [String] The public IP address. + attr_reader :public_ip + + alias_method :ip_address, :public_ip + + # @return [String,nil] Returns the instance id if assigned to an + # EC2 instance + def instance_id; end + describe_call_attribute :instance_id + + # @return [Boolean] Returns true if this IP address is attached to + # an EC2 instance. + def associated? + !!instance_id + end + alias_method :attached?, :associated? + + # @return [Instance,nil] If associated, returns the {Instance} this + # elastic IP address is associated to, nil otherwise. + def instance + associated? ? Instance.new(instance_id, :config => config) : nil + end + + # Releases the elastic IP address. + # + # (For non-VPC elastic ips) Releasing an IP address automatically + # disassociates it from any instance it's associated with. + # + # @return [true] + def delete + client.release_address(:public_ip => public_ip) + true + end + + alias_method :release, :delete + + # Disassociates this elastic IP address from an EC2 instance. + # Raises an exception if this elastic IP is not currently + # associated with an instance. + # @return [true] + def disassociate + client.disassociate_address(:public_ip => public_ip) + true + end + + # @private + # @return [String] Returns the public IP address + def to_s + public_ip.to_s + end + + # @private + protected + def resource_id_method + :public_ip + end + + # @private + protected + def response_id_method + :public_ip + end + + # @private + protected + def find_in_response(resp) + resp.address_index[public_ip] + end + + # @private + protected + def __resource_id__; public_ip; end + + end + end +end diff --git a/lib/aws/ec2/elastic_ip_collection.rb b/lib/aws/ec2/elastic_ip_collection.rb new file mode 100644 index 00000000000..35c5efbd0ad --- /dev/null +++ b/lib/aws/ec2/elastic_ip_collection.rb @@ -0,0 +1,85 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/ec2/collection' +require 'aws/ec2/elastic_ip' + +module AWS + class EC2 + class ElasticIpCollection < Collection + + def create + response = client.allocate_address + ElasticIp.new(response.public_ip, :config => config) + end + + alias_method :allocate, :create + + # @param [String] public_ip The public IP address of an elastic ip. + # @return [ElasticIp] The elastic IP with the given address. + def [] public_ip + super + end + + # Specify one or more criteria to filter elastic IP addresses by. + # A subsequent call to #each will limit the resutls returned + # by provided filters. + # + # * Chain multiple calls of #filter together to AND multiple conditions + # together. + # + # * Supply multiple values to a singler #filter call to OR those + # value conditions together. + # + # * '*' matches one or more characters and '?' matches any one + # character. + # + # === Valid Filters + # + # * domain - Whether the address is a EC2 address, or a VPC address. + # Valid values include 'standard' and 'vpc' + # * instance-id - Instance the address is associated with (if any). + # * public-ip - The Elastic IP address. + # * allocation-id - Allocation ID for the address. For VPC addresses + # only. + # * association-id - Association ID for the address. For VPC addresses + # only. + # + # @return [ElasticIpCollection] A new collection that represents + # a subset of the elastic IP addresses associated with this account. + + # Yields once for each elastic IP address. + # + # @yield [elastic_ip] + # @yieldparam [ElasticIp] elastic_ip + def each &block + response = filtered_request(:describe_addresses) + response.addresses_set.each do |address| + + elastic_ip = ElasticIp.new(address.public_ip, + :config => config) + + yield(elastic_ip) + + end + end + + protected + def member_class + ElasticIp + end + + end + end +end diff --git a/lib/aws/ec2/errors.rb b/lib/aws/ec2/errors.rb new file mode 100644 index 00000000000..22d8867eec6 --- /dev/null +++ b/lib/aws/ec2/errors.rb @@ -0,0 +1,29 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/lazy_error_classes' +require 'aws/ec2/client/xml' + +module AWS + class EC2 + + # @private + module Errors + + BASE_ERROR_GRAMMAR = Client::XML::BaseError + + include LazyErrorClasses + + end + end +end diff --git a/lib/aws/ec2/filtered_collection.rb b/lib/aws/ec2/filtered_collection.rb new file mode 100644 index 00000000000..20f192c2c43 --- /dev/null +++ b/lib/aws/ec2/filtered_collection.rb @@ -0,0 +1,65 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + class EC2 + + # @private + module FilteredCollection + + def initialize options = {} + @filters = options[:filters] || [] + super + end + + # Specify one or more criteria to filter elastic IP addresses by. + # A subsequent call to #each will limit the results returned + # by provided filters. + # + # * Chain multiple calls of #filter together to AND multiple conditions + # together. + # + # * Supply multiple values to a singler #filter call to OR those + # value conditions together. + # + # * '*' matches one or more characters and '?' matches any one + # character. + # + def filter filter_name, *values + filters = @filters.dup + filters << { :name => filter_name, :values => values.flatten } + collection_with(:filters => filters) + end + + # @private + def filtered_request client_method, options = {}, &block + options[:filters] = @filters unless @filters.empty? + client.send(client_method, options) + end + + # @private + protected + def preserved_options + { :config => config, + :filters => @filters } + end + + # @private + protected + def collection_with(options = {}) + self.class.new(preserved_options.merge(options)) + end + + end + end +end diff --git a/lib/aws/ec2/has_permissions.rb b/lib/aws/ec2/has_permissions.rb new file mode 100644 index 00000000000..bb278f26beb --- /dev/null +++ b/lib/aws/ec2/has_permissions.rb @@ -0,0 +1,46 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/permission_collection' + +module AWS + class EC2 + + # Helper methods for managing EC2 resource permissions. See + # {Image} and {Snapshot} for usage examples. + module HasPermissions + + # (see PermissionCollection#public?) + def public? + permissions.public? + end + + # (see PermissionCollection#private?) + def private? + permissions.private? + end + + # (see PermissionCollection#public=) + def public=(value) + permissions.public = value + end + + # @return [PermissionCollection] An object to manage the + # collection of permissions for this resource. + def permissions + PermissionCollection.new(self, :config => config) + end + + end + end +end diff --git a/lib/aws/ec2/image.rb b/lib/aws/ec2/image.rb new file mode 100644 index 00000000000..b80a3c4052b --- /dev/null +++ b/lib/aws/ec2/image.rb @@ -0,0 +1,245 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/resource' +require 'aws/ec2/tagged_item' +require 'aws/ec2/has_permissions' +require 'aws/ec2/instance_collection' + +module AWS + class EC2 + + # Represents an Amazon Machine Image (AMI). + # + # @attr [String] description A description of the image. + # + # @attr_reader [String] location The location of the AMI. + # + # @attr_reader [Symbol] state Current state of the AMI. If the + # state is +:available+, the image is successfully registered + # and available for launching. Valid values: + # + # * +:available+ + # * +:pending+ + # * +:failed+ + # + # @attr_reader [String] owner_id AWS account ID of the image + # owner. + # + # @attr_reader [Symbol] architecture The architecture of the + # image (e.g. +:i386+). + # + # @attr_reader [Symbol] type The type of image. Valid values: + # + # * +:machine+ + # * +:kernel+ + # * +:ramdisk+ + # + # @attr_reader [String] kernel_id The kernel ID associated with + # the image, if any. Only applicable for machine images. + # + # @attr_reader [String] ramdisk_id The RAM disk ID associated + # with the image, if any. Only applicable for machine images. + # + # @attr_reader [String] platform Value is +windows+ for Windows + # AMIs; otherwise blank. + # + # @attr_reader [Object] state_reason The reason for the image's + # most recent state change. The return value is an object with + # the following methods: + # + # [code] Reason code for the state change. + # + # [message] A textual description of the state change. + # + # @attr_reader [String] owner_alias The AWS account alias (e.g., + # +"amazon"+) or AWS account ID that owns the AMI. + # + # @attr_reader [String] name The name of the AMI that was + # provided during image creation. + # + # @attr [String] description A free-form description of the AMI. + # May contain up to 255 characters. + # + # @attr_reader [Symbol] root_device_type The root device type + # used by the AMI. Possible values: + # + # * +:ebs+ + # * +:instance_store+ + # + # @attr_reader [String] root_device_name The root device name + # (e.g., +"/dev/sda1"+, or +"xvda"+). + # + # @attr_reader [Hash] block_device_mappings A hash of block + # device mappings for the image. In each entry, the key is + # the device name (e.g. +"/dev/sda1"+) and the value is an + # object with the following methods that return information + # about the block device: + # + # [snapshot_id] The ID of the snapshot that will be used to + # create this device (may be nil). + # + # [volume_size] The size of the volume, in GiBs. + # + # [delete_on_termination] True if the Amazon EBS volume is + # deleted on instance termination. + # + # @attr_reader [Symbol] virtualization_type The type of + # virtualization of the AMI. Possible values: + # + # * +:paravirtual+ + # * +:hvm+ + # + # @attr_reader [Symbol] hypervisor The image's hypervisor type. + # Possible values: + # + # * +:ovm+ + # * +:xen+ + class Image < Resource + + include TaggedItem + include HasPermissions + alias_method :launch_permissions, :permissions + + # The ID of the AMI. + attr_reader :id + + # @private + def initialize(id, opts = {}) + @id = id + super(opts) + end + + # Deregisters this AMI. Once deregistered, the AMI cannot be + # used to launch new instances. + def deregister + client.deregister_image(:image_id => id) + end + alias_method :delete, :deregister + + # Runs a single instance of this image. + # + # @param [Hash] opts Additional options for running the + # instance. See {InstanceCollection#create} for a full list + # of supported options. + # + # @return [Instance] An object representing the new instance. + def run_instance(opts = {}) + InstanceCollection.new(:config => config). + create(opts.merge(:image => self)) + end + + # Runs multiple instances of this image. + # + # @param count How many instances to request. You can specify + # this either as an integer or as a Range, to indicate the + # minimum and maximum number of instances to run. Note that + # for a new account you can request at most 20 instances at + # a time. + # + # @param [Hash] opts Additional options for running the + # instance. See {InstanceCollection#create} for a full list + # of supported options. + # + # @return [Array] An array containing an {Instance} object for + # each instance that was run. + def run_instances(count, opts = {}) + InstanceCollection.new(:config => config). + create(opts.merge(:image => self, + :count => count)) + end + + # @return [Boolean] +true+ if the AMI exists (is returned by + # the DescribeImages action). + def exists? + resp = client.describe_images(:filters => + [{ :name => "image-id", + :values => [id] }]) and + !resp.images_set.empty? + end + + mutable_attribute :description + # use describe_images to get it, so it will work with public images + describe_call_attribute :description + + describe_call_attribute :name, :memoize => true + describe_call_attribute(:image_location, + :getter => :location, + :memoize => true) + describe_call_attribute :image_state, :getter => :state, :to_sym => true + describe_call_attribute(:image_owner_id, + :getter => :owner_id, + :memoize => true) + describe_call_attribute(:image_owner_alias, + :getter => :owner_alias, + :memoize => true) + describe_call_attribute(:architecture, + :to_sym => true, + :memoize => true) + describe_call_attribute(:image_type, + :getter => :type, + :to_sym => true, + :memoize => true) + + describe_call_attribute :kernel_id, :memoize => true + + # @return [Image] The kernel associated with the image, if + # any. Only applicable for machine images. + def kernel + if id = kernel_id + Image.new(id, :config => config) + end + end + + describe_call_attribute :ramdisk_id, :memoize => true + + # @return [Image] The RAM disk associated with the image, if + # any. Only applicable for machine images. + def ramdisk + if id = ramdisk_id + Image.new(id, :config => config) + end + end + + describe_call_attribute :platform, :memoize => true + describe_call_attribute :state_reason + describe_call_attribute(:root_device_type, + :to_sym => true, + :memoize => true) + describe_call_attribute :root_device_name + describe_call_attribute :virtualization_type, :to_sym => true + describe_call_attribute :hypervisor, :to_sym => true + describe_call_attribute(:block_device_mapping, + :getter => :block_device_mappings) do + translate_output do |mappings| + (mappings || []).inject({}) do |h, mapping| + h[mapping.device_name] = mapping.ebs + h + end + end + end + + # @private + def __permissions_attribute__ + "launchPermission" + end + + protected + def find_in_response(resp) + resp.image_index[id] + end + + end + + end +end diff --git a/lib/aws/ec2/image_collection.rb b/lib/aws/ec2/image_collection.rb new file mode 100644 index 00000000000..8889f5278ce --- /dev/null +++ b/lib/aws/ec2/image_collection.rb @@ -0,0 +1,235 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/ec2/collection' +require 'aws/ec2/tagged_collection' +require 'aws/ec2/image' +require 'aws/ec2/block_device_mappings' + +module AWS + class EC2 + + # Represents a collection of EC2 images. You can use this to + # find out which images exist with the characteristics you are + # interested in: + # + # ec2 = EC2.new + # all_images = ec2.images + # amazon_owned_images = all_images.with_owner('amazon') + # my_images = all_images.with_owner('self') + # tagged_amis = all_images.tagged('mytag') + # tagged_amis.map(&:id) # => ["ami-123", ...] + # + # You can also use it to create new images. For example: + # + # ec2.images.create(:instance_id => "i-123", + # :name => "my-image") + # + class ImageCollection < Collection + + include TaggedCollection + include BlockDeviceMappings + + # @private + def initialize(options = {}) + @owners = options[:owners] || [] + @executable_users = options[:executable_users] || [] + super(options) + end + + # @return [Image] image_id The ID of the image. + def [] image_id + super + end + + # @return [ImageCollection] A new collection that only includes + # images owned by one or more of the specified AWS accounts. + # The IDs +:amazon+ and +:self+ can be used to include AMIs + # owned by Amazon or AMIs owned by you, respectively. + # + # @param [Array of Strings] owners The AWS account IDs by + # which the new collection should be filtered. + def with_owner(*owners) + collection_with(:owners => @owners + owners) + end + + # @return [ImageCollection] A new collection that only includes + # images for which the specified user ID has explicit launch + # permissions. The user ID can be an AWS account ID, +:self+ + # to return AMIs for which the sender of the request has + # explicit launch permissions, or +:all+ to return AMIs with + # public launch permissions. + # + # @param [Array of Strings] users The AWS account IDs by which + # the new collection should be filtered. + def executable_by(*users) + collection_with(:executable_users => @executable_users + users) + end + + # @yield [image] Each image in the collection. + # @return [nil] + def each(&block) + opts = {} + opts[:owners] = @owners.map { |id| id.to_s } unless + @owners.empty? + opts[:executable_users] = @executable_users.map { |id| id.to_s } unless + @executable_users.empty? + response = filtered_request(:describe_images, opts) + response.images_set.each do |i| + image = Image.new(i.image_id, :config => config) + yield(image) + end + nil + end + + # Creates an AMI. There are several ways to create an AMI + # using this method; for detailed information on each strategy + # see {the EC2 Developer + # Guide}[http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/creating-an-ami.html]. + # + # @param [Hash] options Options for creating the image. + # +:name+ is required, and you must also specify one of the + # following options: + # + # * +:instance_id+ + # * +:image_location+ + # * +:root_device_name+ + # + # @option options [String] :instance_id The ID of a running + # instance. This instance will be rebooted unless + # +:no_reboot+ is set to +true+. + # + # @option options [String] :description A description of the + # new image. + # + # @option options [Boolean] :no_reboot By default this + # option is set to +false+, which means Amazon EC2 + # attempts to cleanly shut down the instance before image + # creation and reboots the instance afterwards. When the + # option is set to +true+, Amazon EC2 does not shut down + # the instance before creating the image. When this option + # is used, file system integrity on the created image cannot + # be guaranteed. + # + # *Note*: This option is only valid when used with + # +:instance_id+. + # + # @option options [String] :image_location Full path to your + # AMI manifest in Amazon S3 storage. This must be of the + # form "bucket_name/key". + # + # @option options [String] :architecture The architecture of + # the image. Valid values: + # + # * +:i386+ + # * +:x86_64+ + # + # *Note*: This option is only valid with +:image_location+ + # or +:root_device_name+ + # + # @option options [String] :kernel_id The ID of the kernel to + # select. + # + # *Note*: This option is only valid with +:image_location+ + # or +:root_device_name+ + # + # @option options [Image] :kernel The kernel image to use. + # Equivalent to passing +:kernel_id+ with the ID of the + # image. + # + # *Note*: This option is only valid with +:image_location+ + # or +:root_device_name+ + # + # @option options [String] :ramdisk_id The ID of the RAM disk + # to select. Some kernels require additional drivers at + # launch. Check the kernel requirements for information on + # whether you need to specify a RAM disk. To find kernel + # requirements, refer to the Resource Center and search for + # the kernel ID. + # + # *Note*: This option is only valid with +:image_location+ + # or +:root_device_name+ + # + # @option options [Image] :ramdisk The ramdisk image to use. + # Equivalent to passing +:ramdisk_id+ with the ID of the + # image. + # + # *Note*: This option is only valid with +:image_location+ + # or +:root_device_name+ + # + # @option options [String] :root_device_name The root device + # name (e.g., /dev/sda1, or xvda). + # + # @option options [Hash] :block_device_mappings This must be a + # hash; the keys are device names to map, and the value for + # each entry determines how that device is mapped. Valid + # values include: + # + # * A string, which is interpreted as a virtual device name. + # + # * A hash with any of the following options. One of + # +:snapshot+, +:snapshot_id+ or +:volume_size+ is + # required. + # + # [:snapshot] A snapshot to use when creating the block + # device. + # + # [:snapshot_id] The ID of a snapshot to use when creating + # the block device. + # + # [:volume_size] The size of volume to create, in gigabytes. + # + # [:delete_on_termination] Setting this to true causes EC2 + # to delete the volume when the + # instance is terminated. + # @return [Image] + def create(options = {}) + resp = case + when options[:instance_id] + client.create_image(options) + when options[:image_location] || options[:root_device_name] + if kernel = options.delete(:kernel) + options[:kernel_id] = kernel.id + end + if ramdisk = options.delete(:ramdisk) + options[:ramdisk_id] = ramdisk.id + end + options[:block_device_mappings] = + translate_block_device_mappings(options[:block_device_mappings]) if + options[:block_device_mappings] + client.register_image(options) + else + raise(ArgumentError, + "expected instance_id, image_location, " + + "or root_device_name") + end + self[resp.image_id] + end + + # @private + protected + def member_class + Image + end + + # @private + protected + def preserved_options + super.merge(:owners => @owners, :executable_users => @executable_users) + end + + end + + end +end diff --git a/lib/aws/ec2/instance.rb b/lib/aws/ec2/instance.rb new file mode 100644 index 00000000000..abf56c4aa0a --- /dev/null +++ b/lib/aws/ec2/instance.rb @@ -0,0 +1,515 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/inflection' +require 'aws/ec2/tagged_item' +require 'aws/model' +require 'aws/cacheable' +require 'aws/ec2/resource' +require 'aws/ec2/key_pair' +require 'aws/ec2/image' + +module AWS + class EC2 + + # Represents an EC2 instance. + # + # @attr [String] user_data Arbitrary metadata that is available + # to the instance while it is running. This interface handles + # the details of encoding the user data for transmission; you + # should set the user data exactly as you want it to be made + # available to the instance. + # + # The instance must be in a stopped state to change user data; + # for example: + # + # i.user_data # => "HELLO" + # i.status # => :running + # i.user_data = "GOODBYE" # raises an exception + # i.stop; sleep 1 until i.status == :stopped + # i.user_data = "GOODBYE" # => "GOODBYE" + # + # @attr [String] instance_type The instance type, + # e.g. "m1.small". The instance must be in a stopped state to + # change the instance type. + # + # @attr [Boolean] api_termination_disabled True if the instance + # cannot be terminated using the {#terminate} method. This + # attribute can be changed at any time. + # + # @attr [String] instance_initiated_shutdown_behavior Valid + # values are: + # + # ["stop"] When the instance shuts down, it will go into a + # "stopped" state. + # + # ["terminate"] When the instance shuts down, it will be + # terminated. + # + # @attr_reader [String] image_id Image ID of the AMI used to + # launch the instance. + # + # @attr_reader [String] key_name The name of the key pair with + # which this instance was associated at launch. + # + # @attr [String] kernel_id The ID of the kernel that the image + # currently uses. The instance must be in a stopped state to + # change this attribute. + # + # @attr [String] ramdisk_id The ID of the RAM disk that the + # image currently uses. The instance must be in a stopped + # state to change this attribute. + # + # @attr_reader [Symbol] root_device_type The root device type + # used by the AMI. The AMI can use an Amazon EBS or instance + # store root device. Valid values: + # + # * +:ebs+ + # * +:instance_store+ + # + # @attr_reader [String] root_device_name The name of the root + # device. + # + # @attr_reader [String] private_dns_name The DNS name of the + # instance within the EC2 network. + # + # @attr_reader [String] dns_name The DNS name of the instance on + # the internet. + # + # @attr_reader [Integer] ami_launch_index The AMI launch index, + # which can be used to find this instance within the launch + # group. + # + # @attr_reader [String] private_ip_address The private IP + # address assigned to the instance. + # + # @attr_reader [String] ip_address The IP address of the + # instance. + # + # @attr_reader [Symbol] architecture The architecture of the + # image. + # + # @attr_reader [Symbol] virtualization_type The instance's + # virtualization type. Valid values: + # + # * +:paravirtual+ + # * +:hvm+ + # + # @attr_reader [String] reservation_id The ID of the reservation + # in which this instance was launched. + # + # @attr_reader [String] requester_id ID of the requester that + # launched the instance on your behalf (e.g., AWS Management + # Console, Auto Scaling). + # + # @attr_reader [String] owner_id ID of the AWS account that owns + # the reservation in which the instance was launched. + # + # @attr_reader [Symbol] monitoring The status of CloudWatch + # monitoring for the instance. Valid values: + # + # * +:enabled+ + # * +:disabled+ + # * +:pending+ + # + # @attr_reader [String] state_transition_reason A string + # describing the reason for the last state transition. + # + # @attr_reader [Time] launch_time The time at which the instance + # was launched. + # + # @attr_reader [String] platform A string describing the + # platform of the image (e.g. "windows"). + # + # @attr_reader [Symbol] hypervisor The instance's hypervisor + # type. Valid values: + # + # * +:ovm+ + # * +:xen+ + # + # @attr_reader [String] client_token Idempotency token you + # provided when you launched the instance. + class Instance < Resource + + include TaggedItem + + # Creates an object that represents the instance with the + # given ID. It's usually easier to get an instance of this + # class by calling {InstanceCollection#[]} or + # {InstanceCollection#each}. + def initialize(instance_id, opts = {}) + super + @id = instance_id + @reservation_attributes = {} + end + + # @return [String] Returns the instance id. + attr_reader :id + + # @return [Boolean] True if the instance exists according to + # EC2. + def exists? + client.describe_instances(:filters => + [{ + :name => "instance-id", + :values => [id] + }]).instance_index.key?(id) + end + + # @private + def self.instance_action(name) + define_method(name) do + client.send("#{name}_instances", :instance_ids => [id]) + nil + end + end + + # Terminates the instance. + # @return [nil] + def terminate; end + instance_action :terminate + alias_method :delete, :terminate + + # Reboots the instance. + # @return [nil] + def reboot; end + instance_action :reboot + + # Starts the instance, assuming it is in a stopped state. + # @see stop + # @return [nil] + def start; end + instance_action :start + + # Stops the instance, eventually putting it into a stopped state. + # @return [nil] + def stop; end + instance_action :stop + + describe_call_attribute :private_dns_name + describe_call_attribute :dns_name + alias_method :public_dns_name, :dns_name + describe_call_attribute :ami_launch_index + + describe_call_attribute :monitoring do + translate_output { |v| v.state.to_sym if v } + end + + # @return true if CloudWatch monitoring is enabled for this + # instance. + def monitoring_enabled? + monitoring == :enabled + end + + # Enables or disables monitoring for this instance. + def monitoring_enabled=(value) + if value + client.monitor_instances(:instance_ids => [id]) + true + else + client.unmonitor_instances(:instance_ids => [id]) + false + end + end + + # @return [Array] A list of security groups + # the instance is in. Each member is an instance of + # {SecurityGroup}. + def security_groups + group_set + end + describe_call_attribute :group_set do + translate_output do |groups| + (groups || []).map do |g| + SecurityGroup.new(g.group_id, + :name => g.group_name, + :config => config) + end + end + end + alias_method :groups, :security_groups + + describe_call_attribute :private_ip_address + describe_call_attribute :ip_address + alias_method :public_ip_address, :ip_address + describe_call_attribute :architecture, :to_sym => true + describe_call_attribute :root_device_type, :to_sym => true + describe_call_attribute :instance_lifecycle, :to_sym => true + + # @return [Boolean] true if the instance is a Spot instance. + def spot_instance? + instance_lifecycle == :spot + end + + describe_call_attribute :virtualization_type, :to_sym => true + describe_call_attribute :hypervisor, :to_sym => true + describe_call_attribute :placement + + # @return [String] The availability zone where the instance is + # running. + def availability_zone + if p = placement + p.availability_zone + end + end + + describe_call_attribute :reason, :getter => :state_transition_reason + + describe_call_attribute :launch_time + describe_call_attribute :platform + describe_call_attribute :client_token + describe_call_attribute :image_id + + # @return [Image] The AMI used to launch the instance. + def image + Image.new(image_id, :config => config) + end + + describe_call_attribute :key_name + + # @return [KeyPair] The key pair with which this instance was + # associated at launch. + def key_pair + KeyPair.new(key_name, :config => config) if key_name + end + + # Creates an AMI from this instance. + # + # @param [String] name A name for the new image you're + # creating. Constraints: 3-128 alphanumeric characters, + # parenthesis (()), commas (,), slashes (/), dashes (-), or + # underscores(_) + # + # @param [Hash] options Additional options for creating the + # image. + # + # @option options [String] :description A description of the + # new image. + # + # @option options [Boolean] :no_reboot By default this + # option is set to +false+, which means Amazon EC2 + # attempts to cleanly shut down the instance before image + # creation and reboots the instance afterwards. When the + # option is set to +true+, Amazon EC2 does not shut down + # the instance before creating the image. When this option + # is used, file system integrity on the created image cannot + # be guaranteed. + # + # @return [Image] The newly created image. + def create_image(name, options = {}) + ImageCollection.new(:config => config). + create(options.merge(:instance_id => id, + :name => name)) + end + + # @private + def self.reservation_attribute(name) + define_method(name) do + @reservation_attributes[name] ||= retrieve_attribute(name) do + client.describe_instances(:instance_ids => [id]) + end + end + MetaUtils.class_extend_method(self, :attributes_from_reservation) do |r, i| + if atts = super(r, i) + atts[name] = (r.send(name) if r.respond_to?(name)) + atts + end + end + end + + MetaUtils.class_extend_method(self, :attributes_from_reservation) do |r, i| + attributes_from_response_object(i) if i + end + + reservation_attribute :reservation_id + reservation_attribute :owner_id + reservation_attribute :requester_id + + mutable_attribute :user_data do + # mutable_attribute unwraps the ".value" unless you translate + # the output yourself + translate_output { |v| Base64.decode64(v.value) if v } + translate_input { |v| Base64.encode64(v).strip } + end + + describe_call_attribute :instance_type + mutable_attribute :instance_type + + mutable_attribute :source_dest_check, :getter => :source_dest_check? + mutable_attribute(:disable_api_termination, + :getter => :api_termination_disabled?, + :setter => :api_termination_disabled=) + mutable_attribute :instance_initiated_shutdown_behavior + describe_call_attribute :kernel_id + mutable_attribute :kernel, :getter => :kernel_id, :setter => :kernel_id= + + ## + # Resets the kernel to its default value. + def reset_kernel_id + client.reset_instance_attribute(:instance_id => id, + :attribute => "kernel").return + end + + describe_call_attribute :ramdisk_id + mutable_attribute :ramdisk, :getter => :ramdisk_id, :setter => :ramdisk_id= + + ## + # Resets the RAM disk to its default value. + def reset_ramdisk_id + client.reset_instance_attribute(:instance_id => id, + :attribute => "ramdisk").return + end + + describe_call_attribute :root_device_name + mutable_attribute :root_device_name, :setter => false + + # @return [Hash] A hash in which each key is the name of a + # device that is mapped to an EBS volume. The value of each + # entry is an object describing the volume, including the + # following fields: + # + # [status] The status of the attachment (e.g. "attached"). + # + # [volume_id] The ID of the EBS volume. + # + # [attach_time] The time at which the volume was attached. + # + # [delete_on_termination?] True if the volume will be + # automatically deleted when the + # instance terminates. + def block_device_mappings; end + + describe_call_attribute(:block_device_mapping, + :getter => :block_device_mappings) do + translate_output { |v| translate_block_device_mappings(v) } + end + mutable_attribute(:block_device_mapping, + :setter => false, + :getter => :block_device_mappings) do + translate_output { |v| translate_block_device_mappings(v) } + end + + # @return [Symbol] The status of the instance. Valid values: + # * +:pending+ + # * +:running+ + # * +:shutting_down+ + # * +:terminated+ + # * +:stopping+ + # * +:stopped+ + def status + retrieve_attribute(:status) do + client.describe_instances(:instance_ids => [id]) + end + end + + # Associates the elastic IP address with this instance. + # + # @param [ElasticIp,String] elastic_ip Either a public IP address + # string or an {ElasticIp} object to associate to this + # instance. + # @return [nil] + def associate_elastic_ip elastic_ip + client.associate_address( + :public_ip => elastic_ip.to_s, + :instance_id => self.id) + nil + end + + alias_method :ip_address=, :associate_elastic_ip + + # Disassociates an attached elastic IP address from this instance. + # Raises an exception if there is no elastic IP address associated + # with this instance. + def disassociate_elastic_ip + if ip = self.elastic_ip + ip.disassociate + else + raise "instance #{id} does not have an associated elastic ip" + end + end + + # @return [Boolean] Returns true if an elastic IP address is + # associated with this instance, false otherwise. + def has_elastic_ip? + !elastic_ip.nil? + end + + # @return [ElasticIp,nil] Returns an elastic IP address if one + # is associated with this instance, nil otherwise. + def elastic_ip + ElasticIpCollection.new(:config => config). + filter('instance-id', id).first + end + + protected + def find_in_response(resp) + resp.instance_index[id] + end + + populate_from :describe_instances do |resp| + if r = resp.reservation_index[id] + attributes_from_reservation(r, resp.instance_index[id]) + end + end + + populate_from :run_instances do |resp| + attributes_from_reservation(resp, + resp.instances_set. + find { |i| i.instance_id == id }) + end + + [:terminate_instances, + :start_instances, + :stop_instances].each do |op| + populate_from op do |resp| + if i = resp.instances_set.find { |i| i.instance_id == id } + { :status => i.current_state.name.tr("-","_").to_sym } + end + end + end + + [:monitor_instances, + :unmonitor_instances].each do |op| + populate_from op do |resp| + if i = resp.instances_set.find { |i| i.instance_id == id } + { :monitoring => + attributes_from_response_object(i)[:monitoring] } + end + end + end + + protected + def translate_block_device_mappings(mappings) + mappings.inject({}) do |m, mapping| + m[mapping.device_name] = + Attachment.new(Volume.new(mapping.ebs.volume_id, :config => config), + self, + mapping.device_name, + :config => config) + m + end if mappings + end + + protected + def attributes_from_response_object(obj) + if atts = super(obj) + atts[:status] = obj.instance_state.name.tr("-","_").to_sym if + obj.respond_to?(:instance_state) + atts + end + end + + end + + end +end diff --git a/lib/aws/ec2/instance_collection.rb b/lib/aws/ec2/instance_collection.rb new file mode 100644 index 00000000000..6133e0b754b --- /dev/null +++ b/lib/aws/ec2/instance_collection.rb @@ -0,0 +1,276 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/ec2/tagged_collection' +require 'aws/ec2/collection' +require 'aws/ec2/instance' +require 'aws/ec2/block_device_mappings' +require 'base64' +require 'uuidtools' + +module AWS + class EC2 + + ## + # Represents a collection of EC2 instances. Typically you + # should get an instance of this class by calling + # {EC2#instances}. + # + # To run an instance: + # + # ec2.instances.create(:image_id => "ami-8c1fece5") + # + # To get an instance by ID: + # + # i = ec2.instances["i-12345678"] + # i.exists? + # + # To get a map of instance IDs to instance status: + # + # ec2.instances.inject({}) { |m, i| m[i.id] = i.status; m } + # # => { "i-12345678" => :running, "i-87654321" => :shutting_down } + # + class InstanceCollection < Collection + + include TaggedCollection + include BlockDeviceMappings + + ## + # Runs one or more EC2 instances. + # + # @example Running a single instance + # i = ec2.instances.create(:image_id => "ami-8c1fece5") + # sleep 1 while i.status == :pending + # + # @example Running multiple instances with the same parameters + # instances = + # ec2.instances.create(:image_id => "ami-8c1fece5", + # :count => 10) + # sleep 1 while instances.any? { |i| i.status == :pending } + # + # @example Specifying block device mappings + # ec2.instances.create(:image_id => "ami-8c1fece5", + # :block_device_mappings => { + # "/dev/sda2" => { + # # 15 GiB + # :volume_size => 15, + # :delete_on_termination => true + # } + # }) + # + # @return [Instance or Array] If a single instance is being created, this returns + # an instance of {EC2::Instance} to represent the newly + # created instance. Otherwise it returns an array of + # EC2::Instance objects. + # + # @param [Hash] opts Options for new instance. +:image_id+ is + # the only required option. + # + # @option opts :count How many instances to request. By + # default one instance is requested. You can specify this + # either as an integer or as a Range, to indicate the + # minimum and maximum number of instances to run. Note that + # for a new account you can request at most 20 instances at + # once. + # + # @option opts [Hash] :block_device_mappings This must be a + # hash; the keys are device names to map, and the value for + # each entry determines how that device is mapped. Valid + # values include: + # + # * A string, which is interpreted as a virtual device name. + # + # * The symbol :no_device, which overrides the default + # mapping for a device so that it is not mapped to anything. + # + # * A hash with any of the following options. One of + # +:snapshot+, +:snapshot_id+ or +:volume_size+ is + # required. + # + # [:snapshot] A snapshot to use when creating the block + # device. + # + # [:snapshot_id] The ID of a snapshot to use when creating + # the block device. + # + # [:volume_size] The size of volume to create, in gigabytes. + # + # [:delete_on_termination] Setting this to true causes EC2 + # to delete the volume when the + # instance is terminated. + # + # @option opts [Boolean] :monitoring Setting this to true + # enables CloudWatch monitoring on the instances once they + # are started. + # + # @option opts [String] :availability_zone Specifies the + # availability zone where the instance should run. Without + # this option, EC2 will choose an availability zone for you. + # + # @option opts [String] :image_id ID of the AMI you want to + # launch. + # + # @option opts [String] :key_name The name of the key pair to + # use. Note: Launching public images without a key pair ID + # will leave them inaccessible. + # + # @option opts [Array] :security_groups The names of the + # security groups that will be used to determine network + # access rules for the instances. You may pass instances of + # {SecurityGroup} as well. + # + # @option opts [String] :user_data Arbitrary user data. You + # do not need to encode this value. + # + # @option opts [String] :instance_type The type of instance to + # launch, for example "m1.small". + # + # @option opts [String] :kernel_id The ID of the kernel with + # which to launch the instance. + # + # @option opts [String] :ramdisk_id The ID of the RAM disk to + # select. Some kernels require additional drivers at + # launch. Check the kernel requirements for information on + # whether you need to specify a RAM disk. To find kernel + # requirements, refer to the Resource Center and search for + # the kernel ID. + # + # @option opts [Boolean] :disable_api_termination Specifies + # whether you can terminate the instance using the EC2 + # API. A value of true means you can't terminate the + # instance using the API (i.e., the instance is "locked"); a + # value of false means you can. If you set this to true, and + # you later want to terminate the instance, you must first + # enable API termination. For example: + # + # i = ec2.instances.create(:image_id => "ami-8c1fece5", + # :disable_api_termination => true) + # i.api_termination_disabled? # => true + # i.terminate # raises an exception + # i.api_termination_disabled = false + # i.terminate # terminates the instance + # + # @option opts [String] :instance_initiated_shutdown_behavior + # Determines whether the instance stops or terminates on + # instance-initiated shutdown. + def create(opts = {}) + if image = opts.delete(:image) + opts[:image_id] = image.id + end + + if kernel = opts.delete(:kernel) + opts[:kernel_id] = kernel.id + end + + if ramdisk = opts.delete(:ramdisk) + opts[:ramdisk_id] = ramdisk.id + end + + if key_pair = opts.delete(:key_pair) + opts[:key_name] = key_pair.name + end + + opts = count_opts(opts).merge(opts) + opts.delete(:count) + + opts[:user_data] = Base64.encode64(opts[:user_data]).strip if + opts[:user_data] + + opts[:block_device_mappings] = + translate_block_device_mappings(opts[:block_device_mappings]) if + opts[:block_device_mappings] + + opts[:monitoring] = { :enabled => true } if + opts[:monitoring_enabled] + opts.delete(:monitoring_enabled) + + opts[:placement] = { + :availability_zone => opts[:availability_zone].to_s + } if opts[:availability_zone] + opts.delete(:availability_zone) + + opts[:security_groups] = group_opts(opts[:security_groups]) if + opts[:security_groups] + + opts[:client_token] = UUIDTools::UUID.timestamp_create.to_s + + resp = client.run_instances(opts) + + if opts[:min_count] == opts[:max_count] and + opts[:min_count] == 1 + self[resp.instances_set.first.instance_id] + else + resp.instances_set.map do |i| + self[i.instance_id] + end + end + end + alias_method :run, :create + + # @yield [Instance] Yields each instance in the collection. + def each(&block) + response = filtered_request(:describe_instances) + response.reservation_set.each do |r| + r.instances_set.each do |i| + yield(Instance.new(i.instance_id, :config => config)) + end + end + end + + # @return [Instance] Returns an object representing the EC2 instance + # with the given ID. + def [] id + super + end + + # @private + protected + def member_class + Instance + end + + # @private + private + def count_opts(opts) + min = max = 1 + count = opts[:count] + case count + when Range + min = count.begin + max = (count.exclude_end? ? count.end-1 : count.end) + when Integer + min = max = count + end + { :min_count => min, + :max_count => max } + end + + # @private + private + def group_opts(groups) + [groups].flatten.map do |g| + case g + when SecurityGroup then g.name + when String then g + else + raise ArgumentError.new("members of security_groups must be "+ + "strings or SecurityGroup objects") + end + end + end + + end + + end +end diff --git a/lib/aws/ec2/key_pair.rb b/lib/aws/ec2/key_pair.rb new file mode 100644 index 00000000000..062103bf57f --- /dev/null +++ b/lib/aws/ec2/key_pair.rb @@ -0,0 +1,86 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/resource' + +module AWS + class EC2 + + # Represents an EC2 key pair. + class KeyPair < Resource + + def initialize name, options = {} + @name = name.to_s + @fingerprint = options[:fingerprint] + @private_key = options[:private_key] + super + end + + # @return [String] The name of the key pair. + attr_reader :name + + # @return [Boolean] True if the key pair exists. + def exists? + !client.describe_key_pairs(:filters => [{ :name => "key-name", + :values => [name] }]). + key_set.empty? + end + + # @return [String] A SHA-1 digest of the DER encoded private key + def fingerprint; end + describe_call_attribute(:key_fingerprint, + :getter => :fingerprint, + :memoize => true) + + # Returns the private key. Raises an exception if called + # against an existing key. You can only get the private key + # at the time of creation. + # + # @see KeyPairCollection#import + # @note Only call this method on newly created keys. + # @return [String] An unencrypted PEM encoded RSA private key. + def private_key + unless @private_key + raise 'you can only get the private key for just-created keypairs' + end + @private_key + end + + # Deletes this key pair from EC2. + # @return [true] + def delete + client.delete_key_pair(:key_name => name) + true + end + + [:create_key_pair, + :import_key_pair].each do |op| + populate_from op do |resp| + attributes_from_response_object(resp) if + resp.key_name == name + end + end + + protected + def response_id_method + :key_name + end + + protected + def find_in_response(resp) + resp.key_index[name] + end + + end + end +end diff --git a/lib/aws/ec2/key_pair_collection.rb b/lib/aws/ec2/key_pair_collection.rb new file mode 100644 index 00000000000..225ec55ff2a --- /dev/null +++ b/lib/aws/ec2/key_pair_collection.rb @@ -0,0 +1,102 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/ec2/collection' +require 'aws/ec2/key_pair' +require 'digest/md5' +require 'base64' + +module AWS + class EC2 + + # Represents all key pairs in your account. You can use this collection + # to create, import and find key pairs. + class KeyPairCollection < Collection + + # @param [String] key_name A name for the key pair. + # @return [KeyPair] Returns a new key pair. + def create key_name + create_or_import(:create_key_pair, :key_name => key_name) + end + + # Imports the public key from an RSA key pair that you created with + # a third-party tool. Compare this with {#create}, in which EC2 + # creates the key pair and gives the keys to you (EC2 keeps a copy + # of the public key). With ImportKeyPair, you create the key pair + # and give EC2 just the public key. The private key is never + # transferred between you and EC2. + # + # === Supported formats: + # + # * OpenSSH public key format (e.g., the format in + # ~/.ssh/authorized_keys) + # * Base64 encoded DER format + # * SSH public key file format as specified in RFC4716 + # + # DSA keys are *not* supported. Make sure your key generator is + # set up to create RSA keys. Supported lengths: 1024, 2048, and 4096. + # + # @param [String] key_name A name for this key pair. + # @param [String] public_key The RSA public key. + # @return [KeyPair] Returns a new key pair. + def import key_name, public_key + create_or_import(:import_key_pair, + :key_name => key_name, + :public_key_material => Base64.encode64(public_key.to_s)) + end + + # @return [KeyPair] key_name The name of the key pair. + def [] key_name + super + end + + # Yields once for each key pair in your account. + # @return [nil] + def each &block + response = filtered_request(:describe_key_pairs) + response.key_set.each do |kp| + yield(KeyPair.new(kp.key_name, + :fingerprint => kp.key_fingerprint, + :config => config)) + end + nil + end + + # @private + protected + def member_class + KeyPair + end + + # @private + private + def create_or_import client_method, options + + # stringify option values + options = options.inject({}) {|h,v| h[v.first] = v.last.to_s; h } + response = client.send(client_method, options) + + options = {} + options[:fingerprint] = response.key_fingerprint + if response.respond_to?(:key_material) + options[:private_key] = response.key_material + end + + KeyPair.new(response.key_name, options) + + end + + end + end +end diff --git a/lib/aws/ec2/permission_collection.rb b/lib/aws/ec2/permission_collection.rb new file mode 100644 index 00000000000..973131442dc --- /dev/null +++ b/lib/aws/ec2/permission_collection.rb @@ -0,0 +1,177 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/inflection' + +module AWS + class EC2 + + # Represents the collection of permissions for an EC2 resource. + # Each permission is a string containing the AWS account ID of a + # user who has permission to use the resource in question. The + # {Image} and {Snapshot} classes are currently the only ones + # that use this interface. + class PermissionCollection + + include Model + include Enumerable + + # @private + def initialize(resource, opts = {}) + @resource = resource + super(opts) + end + + # @yield [user_id] Each user ID that has explicit + # permissions to launch this AMI. + def each(&block) + resp = client.send(describe_call, describe_params) + resp.send(inflected_permissions_attribute).each do |permission| + if permission.respond_to?(:user_id) + user_id = permission.user_id + yield(user_id) + end + end + end + + # @return [Integer] The number of users that have explicit + # permissions to launch this AMI. + def size + inject(0) { |sum, i| sum + 1 } + end + + # @return [Boolean] True if the collection is empty. + def empty? + size == 0 + end + + # @return [Boolean] True if the resource is public. + def public? + resp = client.send(describe_call, describe_params) + resp.send(inflected_permissions_attribute).any? do |permission| + permission.respond_to?(:group) and permission.group == "all" + end + end + + # @return [Boolean] True if the resource is private (i.e. not + # public). + def private? + !public? + end + + # Sets whether the resource is public or not. This has no + # effect on the explicit AWS account IDs that may already have + # permissions to use the resource. + # + # @param [Boolean] value If true, the resource is made public, + # otherwise the resource is made private. + # @return [nil] + def public= value + params = value ? + { :add => [{ :group => "all" }] } : + { :remove => [{ :group => "all" }] } + client.send(modify_call, modify_params(params)) + nil + end + + # Adds permissions for specific users to launch this AMI. + # + # @param [Array of Strings] users The AWS account IDs of the + # users that should be able to launch this AMI. + # @return [nil] + def add(*users) + modify(:add, *users) + end + + # Removes permissions for specific users to launch this AMI. + # @param [Array of Strings] users The AWS account IDs of the + # users that should no longer be able to launch this AMI. + # @return [nil] + def remove(*users) + modify(:remove, *users) + end + + # Resets the launch permissions to their default state. + # @return [nil] + def reset + client.send(reset_call, reset_params) + end + + # @private + private + def describe_call + "describe_#{resource_name}_attribute" + end + + # @private + private + def modify_call + "modify_#{resource_name}_attribute" + end + + # @private + private + def reset_call + "reset_#{resource_name}_attribute" + end + + # @private + private + def describe_params + Hash[[["#{resource_name}_id".to_sym, @resource.send(:__resource_id__)], + [:attribute, permissions_attribute]]] + end + alias_method :reset_params, :describe_params + + # @private + private + def inflected_permissions_attribute + Inflection.ruby_name(permissions_attribute).to_sym + end + + # @private + private + def permissions_attribute + @resource.__permissions_attribute__ + end + + # @private + private + def resource_name + @resource.send(:inflected_name) + end + + # @private + private + def modify(action, *users) + return if users.empty? + opts = modify_params(Hash[[[action, + users.map do |user_id| + { :user_id => user_id } + end]]]) + client.send(modify_call, opts) + nil + end + + # @private + private + def modify_params(modifications) + Hash[[["#{resource_name}_id".to_sym, @resource.send(:__resource_id__)], + [inflected_permissions_attribute, modifications]]] + end + + end + + end +end diff --git a/lib/aws/ec2/region.rb b/lib/aws/ec2/region.rb new file mode 100644 index 00000000000..6bc70074900 --- /dev/null +++ b/lib/aws/ec2/region.rb @@ -0,0 +1,81 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/resource' + +module AWS + class EC2 + + # Represents an EC2 region. You can use this to find the + # endpoint for a given region: + # + # ec2.regions["us-west-1"].endpoint + # + # Region also responds to all of the methods of {EC2} except + # {EC2#regions}; for example, to list instance IDs by region, + # you can do: + # + # ec2.regions.inject({}) do |h,region| + # h[region.name] = region.instances.map(&:id) + # h + # end + class Region < Resource + + # The name of the region (e.g. "us-east-1") + attr_reader :name + + def initialize(name, options = {}) + @name = name + super(options) + end + + # @return [String] The endpoint to use for this region. + def endpoint; end + describe_call_attribute :region_endpoint, :getter => :endpoint, :memoize => true + + # @return [Boolean] True if the region is available for this + # account. + def exists? + !client.describe_regions(:filters => [{ :name => "region-name", + :values => [name] }]). + region_info.empty? + end + + PROXIED_METHODS = [:instances, + :security_groups, + :key_pairs, + :elastic_ips, + :tags, + :availability_zones, + :images, + :volumes, + :snapshots, + :reserved_instances, + :reserved_instances_offerings] + + PROXIED_METHODS.each do |method| + define_method(method) do + EC2.new(:config => config, :ec2_endpoint => endpoint). + send(method) + end + end + + protected + def find_in_response(resp) + resp.region_info.find { |r| r.region_name == name } + end + + end + + end +end diff --git a/lib/aws/ec2/region_collection.rb b/lib/aws/ec2/region_collection.rb new file mode 100644 index 00000000000..bda43dcf5d1 --- /dev/null +++ b/lib/aws/ec2/region_collection.rb @@ -0,0 +1,55 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/ec2/collection' +require 'aws/ec2/region' + +module AWS + class EC2 + + # Represents all the regions available to your account. + # + # @example Getting a map of endpoints by region name + # ec2.regions.inject({}) { |m, r| m[r.name] = r.endpoint; m } + class RegionCollection < Collection + + # @yield [Region] Each region that is available to your account. + # @return [nil] + def each + response = filtered_request(:describe_regions) + response.region_info.each do |r| + region = Region.new(r.region_name, + :endpoint => r.region_endpoint, + :config => config) + yield(region) + end + nil + end + + # @return [Region] The region identified by the given name + # (e.g. "us-east-1"). + def [](name) + super + end + + # @private + protected + def member_class + Region + end + + end + + end +end diff --git a/lib/aws/ec2/request.rb b/lib/aws/ec2/request.rb new file mode 100644 index 00000000000..bd5d2fca585 --- /dev/null +++ b/lib/aws/ec2/request.rb @@ -0,0 +1,27 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/http/request' +require 'aws/authorize_v2' + +module AWS + class EC2 + + # @private + class Request < AWS::Http::Request + + include AuthorizeV2 + + end + end +end diff --git a/lib/aws/ec2/reserved_instances.rb b/lib/aws/ec2/reserved_instances.rb new file mode 100644 index 00000000000..81d5e41c0cb --- /dev/null +++ b/lib/aws/ec2/reserved_instances.rb @@ -0,0 +1,50 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/resource' +require 'aws/ec2/tagged_item' + +module AWS + class EC2 + class ReservedInstances < Resource + + ATTRIBUTES = [ + :start, + :instance_count, + :instance_type, + :availability_zone, + :duration, + :fixed_price, + :usage_price, + :product_description, + :instance_tenancy, + :currency_code, + ] + + include TaggedItem + + def initialize id, options = {} + @id = id + super + end + + # @return [String] The id of this reserved instance. + attr_reader :id + + ATTRIBUTES.each do |attr_name| + describe_call_attribute attr_name, :memoize => true + end + + end + end +end diff --git a/lib/aws/ec2/reserved_instances_collection.rb b/lib/aws/ec2/reserved_instances_collection.rb new file mode 100644 index 00000000000..31bbf6e01b5 --- /dev/null +++ b/lib/aws/ec2/reserved_instances_collection.rb @@ -0,0 +1,44 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/collection' +require 'aws/ec2/reserved_instances' +require 'aws/ec2/tagged_collection' + +module AWS + class EC2 + class ReservedInstancesCollection < Collection + + include TaggedCollection + + def member_class + ReservedInstances + end + + def each &block + + response = filtered_request(:describe_reserved_instances) + response.reserved_instances_set.each do |item| + + reserved_instance = ReservedInstances.new( + item.reserved_instances_id, + :config => config) + + yield(reserved_instance) + + end + end + + end + end +end diff --git a/lib/aws/ec2/reserved_instances_offering.rb b/lib/aws/ec2/reserved_instances_offering.rb new file mode 100644 index 00000000000..ac511b5cb50 --- /dev/null +++ b/lib/aws/ec2/reserved_instances_offering.rb @@ -0,0 +1,55 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/resource' +require 'aws/ec2/tagged_item' + +module AWS + class EC2 + class ReservedInstancesOffering < Resource + + ATTRIBUTES = [ + :instance_type, + :availability_zone, + :duration, + :fixed_price, + :usage_price, + :product_description, + :instance_tenancy, + :currency_code, + ] + + include TaggedItem + + def initialize id, options = {} + @id = id + super + end + + # @return [String] The id of this reserved instance offering. + attr_reader :id + + ATTRIBUTES.each do |attr_name| + describe_call_attribute attr_name, :memoize => true + end + + def purchase options = {} + options[:instance_count] = 1 unless options[:instance_count] + options[:reserved_instances_offering_id] = id + response = client.purchase_reserved_instances_offering(options) + ReservedInstances.new(response.reserved_instances_id, :config => config) + end + + end + end +end diff --git a/lib/aws/ec2/reserved_instances_offering_collection.rb b/lib/aws/ec2/reserved_instances_offering_collection.rb new file mode 100644 index 00000000000..c77872280e8 --- /dev/null +++ b/lib/aws/ec2/reserved_instances_offering_collection.rb @@ -0,0 +1,43 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/collection' +require 'aws/ec2/tagged_collection' +require 'aws/ec2/reserved_instances_offering' + +module AWS + class EC2 + class ReservedInstancesOfferingCollection < Collection + + include TaggedCollection + + def member_class + ReservedInstancesOffering + end + + def each &block + response = filtered_request(:describe_reserved_instances_offerings) + response.reserved_instances_offerings_set.each do |item| + + reserved_instance_offering = ReservedInstancesOffering.new( + item.reserved_instances_offering_id, + :config => config) + + yield(reserved_instance_offering) + + end + end + + end + end +end diff --git a/lib/aws/ec2/resource.rb b/lib/aws/ec2/resource.rb new file mode 100644 index 00000000000..aebe79da82e --- /dev/null +++ b/lib/aws/ec2/resource.rb @@ -0,0 +1,340 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/cacheable' + +module AWS + class EC2 + + # @private + class Resource + + include Model + include Cacheable + + def initialize(*args) + super + @memoized_attributes = {} + if options = args.last and + options.kind_of?(Hash) + self.class.memoized_attributes.each do |att| + @memoized_attributes[att] = options[att] if + options.key?(att) + end + end + end + + def inspect + "<#{self.class}:#{__resource_id__}>" + end + + def local_cache_key + "#{self.class}:#{__resource_id__}" + end + + def attributes_from_response(response) + if atts = super + self.class.memoized_attributes.each do |att| + @memoized_attributes[att] = atts[att] + end + atts + end + end + + def ==(other) + other.kind_of?(self.class) and + __resource_id__ == other.__resource_id__ + end + + alias_method :eql?, :== + + protected + def __resource_id__ + send(resource_id_method) + end + + protected + def find_in_response(resp) + resp.send(plural_name + "_set").find do |obj| + obj.send(response_id_method) == __resource_id__ + end + end + + protected + def response_id_method + # e.g. instance_id + inflected_name + "_" + resource_id_method.to_s + end + + protected + def resource_id_method + @resource_id_method ||= + case + when respond_to?(:id) && method(:id).owner != Kernel + # id isn't defined on Object in some Ruby versions, in + # others it is an alias for object_id; if the method is + # not owned by Kernel we can assume that it has been + # overridden in a subclass + :id + when respond_to?(:name) + :name + else + raise NotImplementedError + end + end + + protected + def describe_call + name = self.class.describe_call_name + if client.respond_to?(name) + client.send(name, Hash[[[(response_id_method.to_s + "s").to_sym, + [__resource_id__]]]]) + else + raise NotImplementedError + end + end + + protected + def describe_attribute_call(attribute_name) + name = describe_attribute_call_name + client.send(name, Hash[[[response_id_method.to_sym, + __resource_id__], + [:attribute, attribute_name]]]) + end + + protected + def get_mutable_attribute(name) + att_name = Inflection.class_name(name.to_s) + att_name = att_name[0,1].downcase + att_name[1..-1] + method_name = "describe_#{inflected_name}_attribute" + resp = client.send(method_name, + Hash[[[:"#{inflected_name}_id", __resource_id__], + [:attribute, att_name]]]) + if att = resp.send(name) and + att.respond_to?(:value) + att.value + else + nil + end + end + + protected + def set_mutable_attribute(name, input_value) + value = send("translate_input_for_#{name}", input_value) if + respond_to?("translate_input_for_#{name}") + opts = {} + opts[name] = { :value => value } + opts["#{inflected_name}_id".to_sym] = __resource_id__ + method_name = "modify_#{inflected_name}_attribute" + resp = client.send(method_name, opts) + input_value + end + + protected + def attributes_from_response_object(obj) + atts = {} + self.class.describe_call_attributes.each do |att, response_att| + raw_value = (obj.send(response_att) if obj.respond_to?(response_att)) + translated = send("translate_describe_output_for_#{response_att}", raw_value) + atts[att] = translated + end + atts + end + + protected + def attributes_from_describe_attribute_response(resp) + requested_att = resp.request_options[:attribute] + return nil unless + resp.request_options[response_id_method.to_sym] == __resource_id__ + + response_att = Inflection.ruby_name(requested_att) + if getter = self.class.mutable_attributes[response_att.to_sym] + raw_value = resp.send(response_att) + translated = send("translate_describe_attribute_output_for_#{response_att}", raw_value) + Hash[[[getter, translated]]] + end + end + + module InflectionMethods + + protected + def describe_call_name + name = "describe_#{plural_name}" + end + + protected + def describe_attribute_call_name + "describe_#{inflected_name}_attribute" + end + + protected + def inflected_name + Inflection.ruby_name(class_name) + end + + protected + def class_name + self.kind_of?(Class) ? name : self.class.name + end + + protected + def plural_name + name = inflected_name + name[-1..-1] == 's' ? name : name + "s" + end + + protected + def output_translator(name, type) + "translate_#{type}_output_for_#{name}" + end + + end + + extend InflectionMethods + include InflectionMethods + + # @private + class AttributeBuilder + + include InflectionMethods + + def initialize(klass, name, type) + @klass = klass + @name = name + @type = type + translate_input { |v| v } + translate_output { |v| v } + end + + def translate_input(&blk) + @klass.send(:define_method, "translate_input_for_#{@name}", &blk) + end + + def translate_output(&blk) + @klass.send(:define_method, output_translator(@name, @type), &blk) + end + + def self.eval(klass, name, type, opts = {}, &blk) + i = new(klass, name, type) + i.instance_eval do + translate_output { |v| v.tr("-","_").to_sym if v } if opts[:to_sym] + translate_output { |v| v.value if v } if opts[:value_wrapper] + instance_eval(&blk) if blk + end + end + end + + class NotFound < StandardError; end + + class << self + + def memoized_attributes; []; end + + # @private + protected + def describe_call_attribute(name, opts = {}, &blk) + getter_name = opts[:getter] || name + + # define the accessor + define_describe_call_getter(getter_name, opts) do |*args| + begin + retrieve_attribute(getter_name) { describe_call } + rescue Cacheable::NoData => e + name = Inflection.ruby_name(self.class.name).tr("_", " ") + raise NotFound, "unable to find the #{name}" + end + end + + # ensure we are populating from the describe call + populate_from describe_call_name do |resp| + if obj = find_in_response(resp) + attributes_from_response_object(obj) + end + end + + # add it to the list of attributes to populate from the response object + add_to_attribute_map(:describe_call_attributes, getter_name, name) + + if opts[:memoize] + all_memoized = memoized_attributes + all_memoized << getter_name + MetaUtils.extend_method(self, :memoized_attributes) { all_memoized } + end + + # evaluate translators + AttributeBuilder.eval(self, name, :describe, opts, &blk) + end + + protected + def add_to_attribute_map(map_name, key, value) + all_attributes = {} + all_attributes = send(map_name) if respond_to?(map_name) + all_attributes[key] = value + MetaUtils.extend_method(self, map_name) do + all_attributes + end + end + + protected + def define_describe_call_getter(getter_name, opts, &block) + if opts[:memoize] + original_block = block + block = lambda do + if @memoized_attributes.has_key?(getter_name) + @memoized_attributes[getter_name] + else + instance_eval(&original_block) + end + end + end + define_method(getter_name, &block) + end + + # @private + def mutable_attribute(name, opts = {}, &blk) + getter = opts[:getter] || name + setter = opts[:setter] || "#{name}=" + + # e.g. "instanceType" or "blockDeviceMapping" + # as passed to the :attribute option of the service call + request_name = Inflection.class_name(name.to_s) + request_name = request_name[0,1].downcase + request_name[1..-1] + + # define the getter and setter + define_method(getter) do + retrieve_attribute(getter) { describe_attribute_call(request_name) } + end + define_method(setter) do |input_value| + set_mutable_attribute(name, input_value) + end unless opts[:setter] == false + + # make sure we will populate from this type of response + populate_from describe_attribute_call_name do |resp| + attributes_from_describe_attribute_response(resp) + end + + # describe how to populate this attribute + add_to_attribute_map(:mutable_attributes, name, getter) + + # evaluate translations, etc. + AttributeBuilder.eval(self, name, :describe_attribute, + { :value_wrapper => true }.merge(opts), &blk) + end + + end + + end + + end +end diff --git a/lib/aws/ec2/resource_tag_collection.rb b/lib/aws/ec2/resource_tag_collection.rb new file mode 100644 index 00000000000..6d4a2582573 --- /dev/null +++ b/lib/aws/ec2/resource_tag_collection.rb @@ -0,0 +1,218 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/inflection' +require 'aws/ec2/resource' +require 'aws/ec2/tag' + +module AWS + class EC2 + + # Represents the EC2 tags associated with a single resource. + # + # @example Manipulating the tags of an EC2 instance + # i = ec2.instances["i-123"] + # i.tags.to_h # => { "foo" => "bar", ... } + # i.tags.clear + # i.tags.stage = "production" + # i.tags.stage # => "production" + class ResourceTagCollection + + include Model + include Enumerable + + # @private + def initialize(resource, opts = {}) + @resource = resource + super(opts) + @tags = TagCollection.new(:config => config). + filter("resource-id", @resource.send(:__resource_id__)). + filter("resource-type", @resource.tagging_resource_type) + end + + # @return [String] The value of the tag with the given key, or + # nil if no such tag exists. + # + # @param [String or Symbol] key The key of the tag to return. + def [](key) + if cached = cached_tags + return cached[key.to_s] + end + Tag.new(@resource, key, :config => config).value + rescue Resource::NotFound => e + nil + end + + # @return [Boolean] True if the resource has no tags. + def empty? + if cached = cached_tags + return cached.empty? + end + @tags.to_a.empty? + end + + # @return [Boolean] True if the resource has a tag for the given key. + # + # @param [String or Symbol] The key of the tag to check. + def has_key?(key) + if cached = cached_tags + return cached.has_key?(key.to_s) + end + !@tags.filter("key", key.to_s).to_a.empty? + end + alias_method :key?, :has_key? + alias_method :include?, :has_key? + alias_method :member?, :has_key? + + # @return [Boolean] True if the resource has a tag with the given value. + # + # @param [String or Symbol] The value to check. + def has_value?(value) + if cached = cached_tags + return cached.values.include?(value) + end + !@tags.filter("value", value.to_s).to_a.empty? + end + alias_method :value?, :has_value? + + # Changes the value of a tag. + # + # @param [String or Symbol] The key of the tag to set. + # + # @param [String] The new value. If this is nil, the tag will + # be deleted. + def []=(key, value) + if value + @tags.create(@resource, key.to_s, :value => value) + else + delete(key) + end + end + alias_method :store, :[]= + + # Adds a tag with a blank value. + # + # @param [String or Symbol] key The key of the new tag. + def add(key) + @tags.create(@resource, key.to_s) + end + alias_method :<<, :add + + # Sets multiple tags in a single request. + # + # @param [Hash] tags The tags to set. The keys of the hash + # may be strings or symbols, and the values must be strings. + # Note that there is no way to both set and delete tags + # simultaneously. + def set(tags) + client.create_tags(:resources => [@resource.send(:__resource_id__)], + :tags => tags.map do |(key, value)| + { :key => key.to_s, + :value => value } + end) + end + alias_method :update, :set + + # Allows setting and getting individual tags through instance + # methods. For example: + # + # tags.color = "red" + # tags.color # => "red" + def method_missing(m, *args) + if m.to_s[-1,1] == "=" + send(:[]=, m.to_s[0...-1], *args) + else + super + end + end + + # Deletes the tags with the given keys (which may be strings + # or symbols). + def delete(*keys) + return if keys.empty? + client.delete_tags(:resources => [@resource.send(:__resource_id__)], + :tags => keys.map do |key| + { :key => key.to_s } + end) + end + + # Removes all tags from the resource. + def clear + client.delete_tags(:resources => [@resource.send(:__resource_id__)]) + end + + # @yield [key, value] The key/value pairs of each tag + # associated with the resource. If the block has an arity + # of 1, the key and value will be yielded in an aray. + def each(&blk) + if cached = cached_tags + cached.each(&blk) + return + end + @tags.filtered_request(:describe_tags).tag_set.each do |tag| + if blk.arity == 2 + yield(tag.key, tag.value) + else + yield([tag.key, tag.value]) + end + end + nil + end + alias_method :each_pair, :each + + # @return [Array] An array of the tag values associated with + # the given keys. An entry for a key that has no value + # (i.e. there is no such tag) will be nil. + def values_at(*keys) + if cached = cached_tags + return cached.values_at(*keys.map { |k| k.to_s }) + end + keys = keys.map { |k| k.to_s } + tag_set = @tags. + filter("key", *keys). + filtered_request(:describe_tags).tag_set + hash = tag_set.inject({}) do |hash, tag| + hash[tag.key] = tag.value + hash + end + keys.map do |key| + hash[key] + end + end + + # @return [Hash] The current tags as a hash, where the keys + # are the tag keys as strings and the values are the tag + # values as strings. + def to_h + if cached = cached_tags + return cached + end + @tags.filtered_request(:describe_tags).tag_set.inject({}) do |hash, tag| + hash[tag.key] = tag.value + hash + end + end + + private + def cached_tags + if @resource.respond_to?(:cached_tags) and + cached = @resource.cached_tags + cached + end + end + + end + + end +end diff --git a/lib/aws/ec2/security_group.rb b/lib/aws/ec2/security_group.rb new file mode 100644 index 00000000000..d75c7725f32 --- /dev/null +++ b/lib/aws/ec2/security_group.rb @@ -0,0 +1,246 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/resource' +require 'aws/ec2/tagged_item' +require 'aws/ec2/security_group/ip_permission' +require 'aws/ec2/security_group/ip_permission_collection' + +module AWS + class EC2 + + # Represents a security group in EC2. + class SecurityGroup < Resource + + include TaggedItem + + def initialize id, options = {} + @id = id + @name = options[:name] + @description = options[:description] + @owner_id = options[:owner_id] + super + end + + # @return [String] The id of the security group. + attr_reader :id + + alias_method :group_id, :id + + # @return [Boolean] True if the security group exists. + def exists? + client.describe_security_groups(:filters => + [{ :name => "group-id", + :values => [id] }]). + security_group_index.key?(id) + end + + # @return [String] The name of the security group. + def name; end + describe_call_attribute :group_name, :getter => :name, :memoize => true + + # @return [String] The id of the owner for this security group. + def owner_id; end + describe_call_attribute :owner_id, :memoize => true + + # @return [String] The short informal description given when the + # group was created. + def description; end + describe_call_attribute :group_description, :getter => :description, :memoize => true + + describe_call_attribute :ip_permissions, :getter => :ip_permissions_list + + # @return [SecurityGroup::IpPermissionCollection] Returns a + # collection of {IpPermission} objects that represents all of + # the permissions this security group has authorizations for. + def ip_permissions + IpPermissionCollection.new(self, :config => config) + end + + # Adds ingress rules for ICMP pings. Defaults to 0.0.0.0/0 for + # the list of allowed IP ranges the ping can come from. + # + # security_group.allow_ping # anyone can ping servers in this group + # + # # only allow ping from a particular address + # security_group.allow_ping('123.123.123.123/0') + # + # @param [String] ip_ranges One or more IP ranges to allow ping from. + # Defaults to 0.0.0.0/0 + def allow_ping *sources + sources << '0.0.0.0/0' if sources.empty? + authorize_ingress('icmp', -1, *sources) + end + + # Removes ingress rules for ICMP pings. Defaults to 0.0.0.0/0 for + # the list of IP ranges to revoke. + # + # @param [String] ip_ranges One or more IP ranges to allow ping from. + # Defaults to 0.0.0.0/0 + def disallow_ping *sources + sources << '0.0.0.0/0' if sources.empty? + revoke_ingress('icmp', -1, *sources) + end + + # Adds an ingress rules to a security group. + # + # Each ingress exception is comprised of a protocol a port range + # and a list of sources. + # + # + # This example grants the whole internet (0.0.0.0/0) access to port 80 + # over TCP (HTTP web traffic). + # + # security_groups['websrv'].authorize_ingress(:tcp, 80) + # + # In the following example we grant SSH access from a list of + # IP address. + # + # security_groups['appsrv'].authorize_ingress(:tcp, 22, + # '111.111.111.111/0', '222.222.222.222/0') + # + # You can also grant privileges to other security groups. This + # is a convenient shortcut for granting permissions to all EC2 + # servers in a particular security group access. + # + # web = security_groups['httpservers'] + # db = security_groups['dbservers'] + # + # db.authorize_ingress(:tcp, 3306, web) + # + # You can specify port ranges as well: + # + # security_groups['ftpsvr'].authorize_ingress(:tcp, 20..21) + # + # You can even mix and match IP address and security groups. + # + # @param [String, Symbol] protocol Should be :tcp, :udp or :icmp + # or the string equivalent. + # + # @param [Integer, Range] ports The port (or port range) to allow + # ingress traffic over. You can pass a single integer (like 80) + # or a range (like 20..21). + # + # @param [Mixed] sources One or more CIDR IP addresses, + # security groups, or hashes. Hash values should + # have :group_id and :user_id keys/values. This is useful + # for when the security group belongs to another account. The + # user id should be the owner_id (account id) of the security + # group. + # + # @return [nil] + def authorize_ingress protocol, ports, *sources + permissions = format_permission(protocol, ports, sources) + client.authorize_security_group_ingress( + :group_id => id, + :ip_permissions => permissions) + nil + end + + # @param see #authorize_ingress + # @return [nil] + def revoke_ingress protocol, ports, *sources + permissions = format_permission(protocol, ports, sources) + client.revoke_security_group_ingress( + :group_id => id, + :ip_permissions => permissions) + nil + end + + # Deletes this security group. + # + # If you attempt to delete a security group that contains + # instances, or attempt to delete a security group that is referenced + # by another security group, an error is raised. For example, if + # security group B has a rule that allows access from security + # group A, security group A cannot be deleted until the rule is + # removed. + # @return [nil] + def delete + client.delete_security_group(:group_id => id) + nil + end + + # @private + def resource_type + 'security-group' + end + + # @private + def inflected_name + "group" + end + + # @private + def self.describe_call_name + :describe_security_groups + end + def describe_call_name; self.class.describe_call_name; end + + # @private + protected + def find_in_response(resp) + resp.security_group_index[id] + end + + # @private + protected + def format_permission protocol, ports, sources + + permission = {} + permission[:ip_protocol] = protocol.to_s.downcase + permission[:from_port] = Array(ports).first.to_i + permission[:to_port] = Array(ports).last.to_i + + ip_ranges = [] + groups = [] + + # default to 0.0.0.0/0 + sources << '0.0.0.0/0' if sources.empty? + + sources.each do |where| + case where + + when String + ip_ranges << where + + when SecurityGroup + groups << {:group_id => where.id, :user_id => where.owner_id} + + when Hash + if where.has_key?(:group_id) and where.has_key?(:user_id) + groups << where + else + raise ArgumentError, 'invalid ingress ip permission, hashes ' + + 'must have :group_id and :user_id key/values' + end + else + raise ArgumentError, 'invalid ingress ip permission, ' + + 'expected CIDR IP addres or SecurityGroup' + end + end + + unless ip_ranges.empty? + permission[:ip_ranges] = ip_ranges.collect{|ip| { :cidr_ip => ip } } + end + + unless groups.empty? + permission[:user_id_group_pairs] = groups + end + + [permission] + + end + end + end +end diff --git a/lib/aws/ec2/security_group/ip_permission.rb b/lib/aws/ec2/security_group/ip_permission.rb new file mode 100644 index 00000000000..a5c692c0495 --- /dev/null +++ b/lib/aws/ec2/security_group/ip_permission.rb @@ -0,0 +1,70 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' + +module AWS + class EC2 + class SecurityGroup < Resource + class IpPermission + + include Model + + # @param protocol [:tcp, :udp, :icmp] + # @param port [Range,Integer] An integer or a range of integers + # to open ports for. + # @param [Hash] options + # @option options [Array] :ip_ranges An array of CIDR ip address + # to grant permission to. + # @option options [Array] :groups An array of SecurityGroup objects to + # grant permission to. + def initialize security_group, protocol, ports, options = {} + @security_group = security_group + @protocol = protocol.to_s.downcase.to_sym + @port_range = (Array(ports).first..Array(ports).last) + @ip_ranges = Array(options[:ip_ranges]) + @groups = Array(options[:groups]) + super + end + + # @return [SecurityGroup] The security group this permission is + # authorized for. + attr_reader :security_group + + # @return [Symbol] The protocol (:tcp, :udp, :icmp) + attr_reader :protocol + + # @return [Range] The port range (e.g. 80..80, 4000..4010, etc) + attr_reader :port_range + + # @return [Array] An array if string CIDR ip addresses. + attr_reader :ip_ranges + + # @return [Array] An array of security groups that have been + # granted access with this permission. + attr_reader :groups + + def authorize + sources = groups + ip_ranges + security_group.authorize_ingress(protocol, port_range, *sources) + end + + def revoke + sources = groups + ip_ranges + security_group.revoke_ingress(protocol, port_range, *sources) + end + + end + end + end +end diff --git a/lib/aws/ec2/security_group/ip_permission_collection.rb b/lib/aws/ec2/security_group/ip_permission_collection.rb new file mode 100644 index 00000000000..c0b9e8f504d --- /dev/null +++ b/lib/aws/ec2/security_group/ip_permission_collection.rb @@ -0,0 +1,59 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' + +module AWS + class EC2 + class SecurityGroup < Resource + + class IpPermissionCollection + + include Model + include Enumerable + + attr_reader :security_group + + def initialize(security_group, opts = {}) + super + @security_group = security_group + end + + def each + security_group.ip_permissions_list.each do |p| + + groups = p.groups.collect do |group| + SecurityGroup.new(group.group_id, + :name => group.group_name, + :owner_id => group.user_id, + :config => config) + end + + ip_ranges = p.ip_ranges.collect{|ip| ip.cidr_ip } + + permission = + IpPermission.new(self, p.ip_protocol, [p.from_port, p.to_port], + :ip_ranges => ip_ranges, + :groups => groups, + :config => config) + + yield(permission) + + end + end + + end + + end + end +end diff --git a/lib/aws/ec2/security_group_collection.rb b/lib/aws/ec2/security_group_collection.rb new file mode 100644 index 00000000000..caed70d4e2b --- /dev/null +++ b/lib/aws/ec2/security_group_collection.rb @@ -0,0 +1,132 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/ec2/collection' +require 'aws/ec2/tagged_collection' +require 'aws/ec2/security_group' + +module AWS + class EC2 + + # Represents all EC2 security groups in an AWS account. + class SecurityGroupCollection < Collection + + include TaggedCollection + + # Creates a new + # @param [String] name The name of the security group to create. + # @param [Hash] options + # @option options [String] :description An informal description + # of this security group. Accepts alphanumeric characters, spaces, + # dashes, and underscores. If left blank the description will be set + # to the name. + # @return [SecurityGroup] + def create name, options = {} + + description = options[:description] || name + + response = client.create_security_group( + :group_name => name, + :description => description) + + SecurityGroup.new(response.group_id, { + :name => name, + :description => description, + :config => config }) + + end + + # @param [String] group_id The group id of a security group. + # @return [SecurityGroup] The group with the given id. + def [] group_id + super + end + + # Specify one or more criteria to filter security groups by. + # A subsequent call to #each will limit the security groups returned + # by the set of filters. + # + # If you supply multiple values to #filter then these values are + # treated as an OR condition. To return security groups named + # 'test' or 'fake': + # + # security_groups.filter('group-name', 'test', 'fake') + # + # If you want to and conditions together you need to chain calls to + # filter. To limit security groups to those with a name like + # 'test' and like 'ruby': + # + # security_groups. + # filter('group-name', '*test*'). + # filter('group-name', '*ruby*').each do |group| + # #... + # end + # + # Note that * matches one or more characters and ? matches any one + # character. + # + # === Valid Filters + # + # * description - Description of the security group. + # * group-id - ID of the security group. + # * group-name - Name of the security group. + # * ip-permission.cidr - CIDR range that has been granted the + # permission. + # * ip-permission.from-port - Start of port range for the TCP and UDP + # protocols, or an ICMP type number. + # * ip-permission.group-name - Name of security group that has been + # granted the permission. + # * ip-permission.protocol - IP protocol for the permission. Valid + # values include 'tcp', 'udp', 'icmp' or a protocol number. + # * ip-permission.to-port - End of port range for the TCP and UDP + # protocols, or an ICMP code. + # * ip-permission.user-id - ID of AWS account that has been granted + # the permission. + # * owner-id - AWS account ID of the owner of the security group. + # * tag-key - Key of a tag assigned to the security group. + # * tag-value - Value of a tag assigned to the security group. + # + # @return [SecurityGroupCollection] A new collection that represents + # a subset of the security groups associated with this account. + + # Yields once for each security group in this account. + # + # @yield [group] + # @yieldparam [SecurityGroup] group + # @return [nil] + def each &block + + response = filtered_request(:describe_security_groups) + response.security_group_info.each do |info| + + group = SecurityGroup.new(info.group_id, + :name => info.group_name, + :description => info.group_description, + :owner_id => info.owner_id, + :config => config) + + yield(group) + + end + nil + end + + protected + def member_class + SecurityGroup + end + + end + end +end diff --git a/lib/aws/ec2/snapshot.rb b/lib/aws/ec2/snapshot.rb new file mode 100644 index 00000000000..d47b3bbc067 --- /dev/null +++ b/lib/aws/ec2/snapshot.rb @@ -0,0 +1,138 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/resource' +require 'aws/ec2/has_permissions' +require 'aws/ec2/tagged_item' + +module AWS + class EC2 + + # Represents an Amazon EBS snapshot. + # + # @example Taking a snapshot from a volume + # snapshot = volume.create_snapshot("Database Backup 12/21/2010") + # sleep 1 until [:completed, :error].include?(snapshot.status) + # + # @example Managing snapshot permissions + # unless snapshot.public? + # snapshot.permissions.add("12345678901") + # end + # + # @attr_reader [String] volume_id The ID of the volume this + # snapshot was created from. + # + # @attr_reader [Symbol] status The status of the snapshot. + # Possible values: + # + # * +:pending+ + # * +:completed+ + # * +:error+ + # + # @attr_reader [Time] start_time The time at which the snapshot + # was initiated. + # + # @attr_reader [Integer] progress The progress of the snapshot + # as a percentage. + # + # @attr_reader [String] owner_id The AWS account ID of the + # snapshot owner. + # + # @attr_reader [Integer] volume_size The size of the volume from + # which the snapshot was created. + # + # @attr_reader [String] description The description of the + # snapshot provided at snapshot initiation. + class Snapshot < Resource + + include HasPermissions + include TaggedItem + alias_method :create_volume_permissions, :permissions + + # The snapshot ID + attr_reader :id + + # @private + def initialize(id, opts = {}) + @id = id + super(opts) + end + + # Deletes the snapshot. + def delete + client.delete_snapshot(:snapshot_id => id) + end + + # Creates a volume from the snapshot. + # + # @param [AvailabilityZone or String] availability_zone The + # Availability Zone in which to create the new volume. See + # {EC2#availability_zones} for how to get a list of + # availability zones. + # + # @param [Hash] opts Additional options for creating the volume + # + # @option opts [Integer] size The desired size (in gigabytes) + # for the volume. + # + # @return [Volume] The newly created volume + def create_volume(availability_zone, opts = {}) + VolumeCollection.new(:config => config). + create(opts.merge(:snapshot => self, + :availability_zone => availability_zone)) + end + + # @return [Boolean] True if the snapshot exists. + def exists? + resp = + client.describe_snapshots(:filters => [{ :name => 'snapshot-id', + :values => [id] }]) and + !resp.snapshot_set.empty? + end + + describe_call_attribute :volume_id + + # @return [Volume] The volume this snapshot was created from. + def volume + Volume.new(volume_id, :config => config) if volume_id + end + + describe_call_attribute :status, :to_sym => true + describe_call_attribute :start_time + describe_call_attribute :progress do + translate_output { |value| value.to_i if value } + end + describe_call_attribute :owner_id + describe_call_attribute :volume_size + describe_call_attribute :owner_alias + describe_call_attribute :description + + # @private + def __permissions_attribute__ + "createVolumePermission" + end + + populate_from :create_snapshot do |resp| + attributes_from_response_object(resp) if + resp.snapshot_id == id + end + + protected + def find_in_response(resp) + resp.snapshot_index[id] + end + + end + + end +end diff --git a/lib/aws/ec2/snapshot_collection.rb b/lib/aws/ec2/snapshot_collection.rb new file mode 100644 index 00000000000..918bce6ac96 --- /dev/null +++ b/lib/aws/ec2/snapshot_collection.rb @@ -0,0 +1,90 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/collection' +require 'aws/ec2/tagged_collection' +require 'aws/ec2/snapshot' + +module AWS + class EC2 + + # Represents a collection of Amazon EBS snapshots. Typically + # you should get an instance of this class by calling + # {EC2#snapshots}. + # + # @example Create a snapshot from a volume + # ec2.snapshots.create(:volume => ec2.volumes["vol-123"], + # :description => "my snapshot") + # # or: + # ec2.volumes["vol-123"].create_snapshot("my snapshot") + # + # @example Get a snapshot by ID + # snapshot = ec2.snapshots["vol-123"] + # snapshot.exists? + # + # @example Get a map of snapshot IDs to snapshot status + # ec2.snapshots.inject({}) { |m, s| m[i.id] = s.status; m } + # # => { "snap-12345678" => :pending, "snap-87654321" => :completed } + class SnapshotCollection < Collection + + include TaggedCollection + + # @yield [Instance] Yields each volume in the collection. + # @return [nil] + def each(&block) + resp = filtered_request(:describe_snapshots) + resp.snapshot_set.each do |v| + snapshot = Snapshot.new(v.snapshot_id, :config => config) + yield(snapshot) + end + nil + end + + # Creates a snapshot of an Amazon EBS volume and stores it in + # Amazon S3. You can use snapshots for backups, to make + # identical copies of instance devices, and to save data + # before shutting down an instance. For more information about + # Amazon EBS, go to the {Amazon Elastic Compute Cloud User + # Guide}[http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/index.html?using-ebs.html]. + # + # @return [Snapshot] An object representing the new snapshot. + # + # @param [Hash] opts Options for creating the snapshot. + # Either +:volume+ or +:volume_id+ is required. + # + # @param opts [Volume] :volume The Amazon EBS volume of which + # to take a snapshot. + # + # @param opts [String] :volume_id The ID of the Amazon EBS + # volume of which to take a snapshot. + # + # @param opts [String] :description An optional description of + # the snapshot. May contain up to 255 characters. + def create opts = {} + if volume = opts.delete(:volume) + opts[:volume_id] = volume.id + end + resp = client.create_snapshot(opts) + Snapshot.new(resp.snapshot_id, :config => config) + end + + # @private + protected + def member_class + Snapshot + end + + end + + end +end diff --git a/lib/aws/ec2/tag.rb b/lib/aws/ec2/tag.rb new file mode 100644 index 00000000000..e700ae6f533 --- /dev/null +++ b/lib/aws/ec2/tag.rb @@ -0,0 +1,88 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/cacheable' + +module AWS + class EC2 + + # Represents an EC2 tag. + class Tag < Resource + + # @param [String] key The name of the tag + # @param [Hash] options + # @option options [String] :value ('') The optional value of the tag. + def initialize resource, key, options = {} + @resource = resource + @key = key.to_s + super + end + + # @return [Object] The EC2 resource this tag belongs to. + attr_reader :resource + + # @return [String] The name of this tag. + attr_reader :key + + alias_method :name, :key + + # @return [String] The tag value. + def value; end + describe_call_attribute :value + + # Deletes this tag. + # @return [nil] + def delete(value = nil) + tag_opts = { :key => key } + tag_opts[:value] = value if value + client.delete_tags(:resources => [resource.id], :tags => [tag_opts]) + nil + end + + # @private + def inspect + "<#{local_cache_key}>" + end + + # @private + def local_cache_key + "#{self.class}:#{response_index_key}" + end + + private + def response_index_key + type = resource.tagging_resource_type + id = resource.send(:__resource_id__) + "#{type}:#{id}:#{key}" + end + + protected + def find_in_response(resp) + resp.tag_index[response_index_key] + end + + protected + def describe_call + client.describe_tags(:filters => + [{ :name => "key", + :values => [key] }, + { :name => "resource-type", + :values => [resource.tagging_resource_type] }, + { :name => "resource-id", + :values => [resource.send(:__resource_id__)] }]) + end + + end + end +end diff --git a/lib/aws/ec2/tag_collection.rb b/lib/aws/ec2/tag_collection.rb new file mode 100644 index 00000000000..674d4b9de3e --- /dev/null +++ b/lib/aws/ec2/tag_collection.rb @@ -0,0 +1,114 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/inflection' +require 'aws/ec2/collection' +require 'aws/ec2/tag' +require 'aws/ec2/tagged_item' +require 'aws/ec2/resource' + +module AWS + class EC2 + + # Temporary class that will be removed once the rest of the taggable + # EC2 resources have been modeled. + class ResourceObject < Resource + + def initialize id, options = {} + @id = id + @resource_type = options[:resource_type] + super + end + + attr_reader :id + + include TaggedItem + + # @private + def tagging_resource_type + @resource_type + end + + # @private + # We don't know how to make a describe call for this object yet + def cached_tags; nil; end + + end + + # Represents all EC2 tags in an account. + class TagCollection < Collection + + # Creates a new Tag and assigns it to an EC2 resource. + # + # @example tagging with names (keys) only + # + # ec2.tags.create(instance, 'webserver') + # + # @example tagging with names (keys) and values + # + # ec2.tags.create(instance, 'stage', 'production') + # + # @param [Object] resource The item to tag. This should be a taggable + # EC2 resource, like an instance, security group, etc. + # @param [String] key The tag key (or name). + # @param [Hash] options + # @option optins [String] :value ('') The optional tag value. When + # left blank its assigned the empty string. + # @return [Tag] + def create resource, key, options = {} + value = options[:value].to_s + client.create_tags( + :resources => [resource.id], + :tags => [{ :key => key, :value => value }]) + Tag.new(resource, key, :value => value, :config => config) + end + + # @return [Tag] Returns a reference to a tag with the given name. + def [] tag_name + super + end + + # Yields once for each tag. + # @yield [tag] + # @yieldparam [Tag] tag + # @return [nil] + def each &block + response = filtered_request(:describe_tags) + response.tag_set.each do |tag| + + resource_class_name = Inflection.class_name(tag.resource_type) + if EC2.const_defined?(resource_class_name) + resource_class = EC2.const_get(resource_class_name) + resource = resource_class.new(tag.resource_id, :config => config) + else + resource = ResourceObject.new(tag.resource_id, + :resource_type => tag.resource_type, + :config => config) + end + + yield(Tag.new(resource, tag.key)) + + end + nil + end + + # @private + protected + def member_class + Tag + end + + end + end +end diff --git a/lib/aws/ec2/tagged_collection.rb b/lib/aws/ec2/tagged_collection.rb new file mode 100644 index 00000000000..2c24a0fd8d0 --- /dev/null +++ b/lib/aws/ec2/tagged_collection.rb @@ -0,0 +1,48 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + class EC2 + + # @private + module TaggedCollection + + # Filter the collection by one or more tag keys. If you pass multiple + # tag keys they will be be treated as OR conditions. If you want to + # AND them together call tagged multiple times (chained). + # + # Filter the collection to items items tagged 'live' OR 'test' + # + # collection.tagged('live', 'test') + # + # Filter the collection to items tagged 'live' AND 'webserver' + # + # collection.tagged('live').tagged('webserver') + # + def tagged *keys + filter('tag-key', *keys) + end + + # Filter the collection by one or more tag values. If you pass multiple + # tag values they will be be treated as OR conditions. If you want to + # AND them together call tagged multiple times (chained). + # + # collection.tagged('stage').tagged_values('production') + # + def tagged_values *values + filter('tag-value', *values) + end + + end + end +end diff --git a/lib/aws/ec2/tagged_item.rb b/lib/aws/ec2/tagged_item.rb new file mode 100644 index 00000000000..a936e44fd4c --- /dev/null +++ b/lib/aws/ec2/tagged_item.rb @@ -0,0 +1,87 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/resource_tag_collection' + +module AWS + class EC2 + module TaggedItem + + # Adds a single tag with an optional tag value. + # + # # adds a tag with the key production + # resource.tag('production') + # + # # adds a tag with the optional value set to production + # resource.tag('role', :value => 'webserver') + # + # @param [String] key The name of the tag to add. + # @param [Hash] options + # @option options [String] :value An optional tag value. + # @return [Tag] The tag that was created. + def add_tag key, options = {} + client.create_tags({ + :resources => [id], + :tags => [{ :key => key, :value => options[:value].to_s }], + }) + Tag.new(self, key, options.merge(:config => config)) + end + alias_method :tag, :add_tag + + # Deletes all tags associated with this EC2 resource. + # @return [nil] + def clear_tags + client.delete_tags(:resources => [self.id]) + nil + end + + # Returns a collection that represents only tags belonging to + # this resource. + # + # @example Manipulating the tags of an EC2 instance + # i = ec2.instances["i-123"] + # i.tags.to_h # => { "foo" => "bar", ... } + # i.tags.clear + # i.tags.stage = "production" + # i.tags.stage # => "production" + # + # @return [ResourceTagCollection] A collection of tags that + # belong to this resource. + # + def tags + ResourceTagCollection.new(self, :config => config) + end + + # @private + def cached_tags + if cache = AWS.response_cache + cache.select(describe_call_name.to_sym).each do |resp| + if obj = find_in_response(resp) + return obj.tag_set.inject({}) do |hash, tag| + hash[tag.key] = tag.value + hash + end + end + end + end + nil + end + + # @private + def tagging_resource_type + Inflection.ruby_name(self.class.to_s).tr("_","-") + end + + end + end +end diff --git a/lib/aws/ec2/volume.rb b/lib/aws/ec2/volume.rb new file mode 100644 index 00000000000..2d2f2dc6eb3 --- /dev/null +++ b/lib/aws/ec2/volume.rb @@ -0,0 +1,190 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/resource' +require 'aws/ec2/tagged_item' +require 'aws/ec2/availability_zone' +require 'aws/ec2/attachment' +require 'aws/ec2/attachment_collection' + +module AWS + class EC2 + + # Represents an Amazon EBS volume. + # + # @example Create an empty 15GiB volume and attach it to an instance + # volume = ec2.volumes.create(:size => 15, + # :availability_zone => "us-east-1a") + # attachment = volume.attach_to(ec2.instances["i-123"], "/dev/sdf") + # sleep 1 until attachment.status != :attaching + # + # @example Remove all attachments from a volume and then delete it + # volume.attachments.each do |attachment| + # attachment.delete(:force => true) + # end + # sleep 1 until volume.status == :available + # volume.delete + # + # @attr_reader [Symbol] status The status of the volume. + # Possible values: + # + # * +:creating+ + # * +:available+ + # * +:in_use+ + # * +:deleting+ + # * +:deleted+ + # * +:error+ + # + # @attr_reader [Integer] size The size of the volume in + # gigabytes. + # + # @attr_reader [AvailabilityZone] availability_zone Availability + # Zone in which the volume was created. + # + # @attr_reader [Time] create_time The time at which the volume + # was created. + class Volume < Resource + + include TaggedItem + + attr_reader :id + + # @private + def initialize(id, opts = {}) + @id = id + super(opts) + end + + # Deletes the volume. + def delete + client.delete_volume(:volume_id => id) + end + + # @return [Snapshot] A new snapshot created from the volume. + # + # @param [String] description An optional description of the + # snapshot. May be up to 255 characters in length. + def create_snapshot(description = nil) + opts = { :volume => self } + opts[:description] = description if description + SnapshotCollection.new(:config => config). + create(opts) + end + + # Attaches the volume to an instance. + # + # @param [Instance] instance The instance to which the volume + # attaches. The volume and instance must be within the same + # Availability Zone and the instance must be running. + # + # @param [String] device How the device is exposed to the + # instance (e.g., /dev/sdh, or xvdh). + # + # @return [Attachment] An object representing the attachment, + # which you can use to query the attachment status. + def attach_to(instance, device) + resp = client.attach_volume(:volume_id => id, + :instance_id => instance.id, + :device => device) + Attachment.new(self, + Instance.new(resp.instance_id), + resp.device, + :config => config) + end + alias_method :attach, :attach_to + + # Detaches the volume from an instance. + # + # @param [Instance] instance The instance to detach from. + # + # @param [String] device The device name. + # + # @param [Hash] opts Additional options for detaching the + # volume. + # + # @option opts [Boolean] :force Forces detachment if the + # previous detachment attempt did not occur cleanly (logging + # into an instance, unmounting the volume, and detaching + # normally). This option can lead to data loss or a + # corrupted file system. Use this option only as a last + # resort to detach a volume from a failed instance. The + # instance will not have an opportunity to flush file system + # caches or file system metadata. If you use this option, + # you must perform file system check and repair procedures. + def detach_from(instance, device, opts = {}) + a = Attachment.new(self, + Instance.new(instance.id), + device, + :config => config) + a.delete + a + end + + # @return [AttachmentCollection] The collection of attachments + # that involve this volume. + def attachments + AttachmentCollection.new(self, :config => config) + end + + # @return [Boolean] True if the volume exists. + def exists? + resp = + client.describe_volumes(:filters => [{ :name => 'volume-id', + :values => [id] }]) and + resp.volume_index.key?(id) + end + + describe_call_attribute :status, :to_sym => true + alias_method :state, :status + + describe_call_attribute :snapshot_id + + # @return [Snapshot] Snapshot from which the volume was created + # (may be nil). + def snapshot; + if snapshot_id = self.snapshot_id + Snapshot.new(snapshot_id, :config => config) + end + end + + describe_call_attribute :size do + translate_output { |value| value.to_i if value } + end + + describe_call_attribute :availability_zone, :getter => :availability_zone_name + + def availability_zone + if name = availability_zone_name + AvailabilityZone.new(name, :config => config) + end + end + + describe_call_attribute :create_time + + describe_call_attribute :attachment_set + + populate_from :create_volume do |resp| + attributes_from_response_object(resp) if + resp.volume_id == id + end + + # @private + protected + def find_in_response(resp) + resp.volume_index[id] + end + + end + + end +end diff --git a/lib/aws/ec2/volume_collection.rb b/lib/aws/ec2/volume_collection.rb new file mode 100644 index 00000000000..1cbf5e46dc8 --- /dev/null +++ b/lib/aws/ec2/volume_collection.rb @@ -0,0 +1,95 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/ec2/collection' +require 'aws/ec2/tagged_collection' +require 'aws/ec2/volume' + +module AWS + class EC2 + + # Represents a collection of Amazon EBS volumes. Typically you + # should get an instance of this class by calling {EC2#volumes}. + # + # @example Create an empty 15GiB volume + # ec2.volumes.create(:size => 15, + # :availability_zone => "us-east-1a") + # + # @example Get a volume by ID + # volume = ec2.volumes["vol-123"] + # volume.exists? + # + # @example Get a map of volume IDs to volume status + # ec2.volumes.inject({}) { |m, v| m[i.id] = v.status; m } + # # => { "vol-12345678" => :available, "vol-87654321" => :in_use } + class VolumeCollection < Collection + + include TaggedCollection + + # @yield [Instance] Yields each volume in the collection. + # @return [nil] + def each(&block) + resp = filtered_request(:describe_volumes) + resp.volume_set.each do |v| + volume = Volume.new(v.volume_id, :config => config) + yield(volume) + end + nil + end + + # Creates a new Amazon EBS volume that any Amazon EC2 instance + # in the same Availability Zone can attach to. For more + # information about Amazon EBS, go to the {Amazon Elastic + # Compute Cloud User + # Guide}[http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/index.html?using-ebs.html]. + # + # @return [Volume] An object representing the new volume. + # + # @param [Hash] opts Options for creating the volume. + # +:availability_zone+ and one of +:size+, +:snapshot+, or + # +:snapshot_id+ is required. + # + # @option opts [Integer] :size The size of the volume, in + # GiBs. Valid values: 1 - 1024. If +:snapshot+ or + # +:snapshot_id+ is specified, this defaults to the size of + # the specified snapshot. + # + # @option opts [Snapshot] :snapshot The snapshot from which to + # create the new volume. + # + # @option opts [String] :snapshot_id The ID of the snapshot + # from which to create the new volume. + # + # @option opts [String, AvailabilityZone] :availability_zone + # The Availability Zone in which to create the new volume. + # To get a list of the availability zones you can use, see + # {EC2#availability_zones}. + # @return [Volume] + def create(opts = {}) + if snapshot = opts.delete(:snapshot) + opts[:snapshot_id] = snapshot.id + end + resp = client.create_volume(opts) + Volume.new(resp.volume_id, :config => config) + end + + # @private + protected + def member_class + Volume + end + + end + + end +end diff --git a/lib/aws/errors.rb b/lib/aws/errors.rb new file mode 100644 index 00000000000..b719536b6d6 --- /dev/null +++ b/lib/aws/errors.rb @@ -0,0 +1,129 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'thread' +require 'aws/xml_grammar' + +module AWS + module Errors + + # Base class for the two service error classes: + # + # * {ClientError} + # * {ServerError} + # + # When interacting with Amazon AWS services, you will sometimes + # receive a non-200 level response. These http responses are treated + # as errors. + # + # == Client Errors + # + # Errors in the three and four hundreds are client errors ({ClientError}). + # A client error should not be resent without changes. The body of the + # http response (the error #message) should give more information about + # the nature of the problem. + # + # == Server Errors + # + # A 500 level error typically indicates the service is having an issue. + # + # Requests that generate service errors are automatically retried with + # an exponential backoff. If the service still fails to respond with + # a 200 after 3 retries the error is raised. + # + class Base < StandardError + + # @return [Http::Request] The low level http request that caused the + # error to be raised. + attr_reader :http_request + + # @return [Http::Response] The low level http response from the service + # that wrapped the service error. + attr_reader :http_response + + def initialize http_request, http_response, message = http_response.body + @http_request = http_request + @http_response = http_response + super(message) + end + + end + + # @private + module ExceptionMixinClassMethods + def new(*args) + e = Base.new(*args) + e.extend(self) + e + end + end + + # Raised when an error occurs as a result of bad client + # behavior, most commonly when the parameters passed to a method + # are somehow invalid. Other common cases: + # + # * Throttling errors + # * Bad credentials + # * No permission to do the requested operation + # * Limits exceeded (e.g. too many buckets) + # + module ClientError + extend ExceptionMixinClassMethods + end + + # Raised when an AWS service is unable to handle the request. These + # are automatically retired. If after 3 retries the request is still + # failing, then the error is raised. + module ServerError + extend ExceptionMixinClassMethods + end + + # @private + module ModeledError + + # @return [String] The error message given by the AWS service. + attr_accessor :message + + # @return [Integer] The HTTP status code returned by the AWS service. + attr_reader :code + + def initialize(req, resp) + super(req, resp, message) + include_error_type + parse_body(resp.body) + end + + def include_error_type + if http_response.status >= 500 + extend ServerError + else + extend ClientError + end + end + + def parse_body(body) + error_grammar.parse(body, :context => self) + end + + def extract_message(body) + error_grammar.parse(body).message + end + + def error_grammar + self.class::BASE_ERROR_GRAMMAR + end + + end + + end +end diff --git a/lib/aws/http/builtin_handler.rb b/lib/aws/http/builtin_handler.rb new file mode 100644 index 00000000000..aa016163fef --- /dev/null +++ b/lib/aws/http/builtin_handler.rb @@ -0,0 +1,69 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'net/http' +require 'net/https' + +module AWS + module Http + + # @private + class BuiltinHandler + + def handle(request, response) + + http = Net::HTTP.new(request.host, request.use_ssl? ? 443 : 80) + + # http://www.ruby-lang.org/en/news/2007/10/04/net-https-vulnerability/ + if request.use_ssl? + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_PEER + store = OpenSSL::X509::Store.new + store.set_default_paths + http.cert_store = store + end + + http.start do + + # Net::HTTP adds this header for us when the body is + # provided, but it messes up signing + headers = { 'content-type' => '' } + + # Net::HTTP calls strip on each header value, this causes errors + # when the values are numbers (like Content-Length) + request.headers.each_pair do |key,value| + headers[key] = value.to_s + end + + http_request_class = case request.http_method + when 'HEAD' then Net::HTTP::Head + when 'GET' then Net::HTTP::Get + when 'PUT' then Net::HTTP::Put + when 'POST' then Net::HTTP::Post + when 'DELETE' then Net::HTTP::Delete + else raise "unsupported http request method: #{request.http_method}" + end + + http_request = http_request_class.new(request.uri, headers) + http.request(http_request, request.body) do |http_response| + response.body = http_response.body + response.status = http_response.code.to_i + response.headers = http_response.to_hash + end + + end + end + + end + end +end diff --git a/lib/aws/http/curb_handler.rb b/lib/aws/http/curb_handler.rb new file mode 100644 index 00000000000..5b2bbd8de02 --- /dev/null +++ b/lib/aws/http/curb_handler.rb @@ -0,0 +1,123 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'thread' + +module AWS + module Http + + # @private + class CurbHandler + + def initialize + @q = [] + @sem = Mutex.new + @multi = Curl::Multi.new + + start_processor + end + + def handle request, response + raise "unsupport http reqest method: #{request.http_method}" unless + ['GET', 'HEAD', 'PUT', 'POST', 'DELETE'].include? request.http_method + @sem.synchronize do + @q << [request, response, Thread.current] + @processor.wakeup + end + Thread.stop + nil + end + + # fills the Curl::Multi handle with the given array of queue + # items, calling make_easy_handle on each one first + private + def fill_multi(items) + items.each do |item| + c = make_easy_handle(*item) + @multi.add(c) + end + end + + # starts a background thread that waits for new items and + # sends them through the Curl::Multi handle + private + def start_processor + @processor = Thread.new do + loop do + items = nil + @sem.synchronize do + items = @q.slice!(0..-1) + end + unless items.empty? + fill_multi(items) + @multi.perform do + # curl is idle, so process more items if we can get them + # without blocking + if !@q.empty? && @sem.try_lock + begin + fill_multi(@q.slice!(0..-1)) + ensure + @sem.unlock + end + end + end + end + + # wait for a new item to show up before continuing + Thread.stop if @q.empty? + end + end + end + + private + def make_easy_handle request, response, thread = nil + + url = request.use_ssl? ? + "https://#{request.host}:443#{request.uri}" : + "http://#{request.host}#{request.uri}" + puts url + + curl = Curl::Easy.new(url) + curl.headers = request.headers + + curl.on_header {|header_data| + name, value = header_data.strip.split(/:\s+/, 2) + response.headers[name] = value + header_data.length + } + + case request.http_method + when 'GET' + # .... + when 'HEAD' + curl.head = true + when 'PUT' + curl.put_data = request.body + when 'POST' + curl.post_body = request.body + when 'DELETE' + curl.delete = true + end + + curl.on_complete do + response.body = curl.body_str + response.status = curl.response_code + thread.run if thread + end + + curl + end + + end + end +end diff --git a/lib/aws/http/handler.rb b/lib/aws/http/handler.rb new file mode 100644 index 00000000000..913597536f7 --- /dev/null +++ b/lib/aws/http/handler.rb @@ -0,0 +1,77 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/meta_utils' + +module AWS + module Http + + # @private + class Handler + + attr_reader :base + + def initialize(base, &block) + @base = base + if base.respond_to?(:handle) + + unless block.arity == 2 + raise ArgumentError, 'passed block must accept 2 arguments' + end + MetaUtils.extend_method(self, :handle, &block) + + elsif base.respond_to?(:handle_async) + + unless block.arity == 3 + raise ArgumentError, 'passed block must accept 3 arguments' + end + + MetaUtils.extend_method(self, :handle_async) do |req, resp, handle| + @base.handle_async(req, resp, handle) + end + MetaUtils.extend(self) do + define_method(:handle) do |req, resp| + raise "attempted to call #handle on an async handler" + end + define_method(:handle_async, &block) + end + + else + raise ArgumentError, 'base must respond to #handle or #handle_async' + end + end + + def handle(request, http_response) + @base.handle(request, http_response) + end + + def handle_async(request, http_response, handle) + Thread.new do + begin + self.handle(request, http_response) + rescue => e + handle.signal_failure + else + handle.signal_success + end + end + end + + def sleep_with_callback seconds, &block + Kernel.sleep(seconds) + yield + end + + end + end +end diff --git a/lib/aws/http/httparty_handler.rb b/lib/aws/http/httparty_handler.rb new file mode 100644 index 00000000000..0d78c4bc1f8 --- /dev/null +++ b/lib/aws/http/httparty_handler.rb @@ -0,0 +1,61 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'httparty' + +module AWS + module Http + + # @private + class HTTPartyHandler + + include HTTParty + + class NoOpParser < HTTParty::Parser + SupportedFormats = {} + end + + def handle(request, response) + + url = request.use_ssl? ? + "https://#{request.host}:443#{request.uri}" : + "http://#{request.host}#{request.uri}" + + # get, post, put, delete, head + method = request.http_method.downcase + + # Net::HTTP adds this header for us when the body is + # provided, but it messes up signing + headers = { 'content-type' => '' } + + # headers must have string values (net http calls .strip on them) + request.headers.each_pair do |key,value| + headers[key] = value.to_s + end + + begin + http_response = self.class.send(method, url, + :headers => headers, + :body => request.body, + :parser => NoOpParser) + rescue Timeout::Error => e + response.timeout = true + else + response.body = http_response.body + response.status = http_response.code.to_i + response.headers = http_response.to_hash + end + end + end + end +end diff --git a/lib/aws/http/request.rb b/lib/aws/http/request.rb new file mode 100644 index 00000000000..82dbf1ff897 --- /dev/null +++ b/lib/aws/http/request.rb @@ -0,0 +1,136 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'stringio' +require 'aws/http/request_param' + +module AWS + module Http + + # Base class for all service reqeusts. + # @private + class Request + + # Returns a new empty http request object. + def initialize + @host = nil + @http_method = 'POST' + @path = '/' + @headers = CaseInsensitiveHash.new + @params = [] + @use_ssl = true + end + + # @return [String] hostname of the request + attr_accessor :host + + # @return [CaseInsensitiveHash] request headers + attr_reader :headers + + # @return [Array] An array of request params, each param responds to + # #name and #value. + attr_reader :params + + # @return [String] GET, PUT POST, HEAD or DELETE, defaults to POST + attr_accessor :http_method + + # @return [String] path of the request URI, defaults to / + attr_reader :path + + # @return [String] the AWS access key ID used to authorize the + # request + attr_accessor :access_key_id + + # @param [Boolean] ssl If the request should be sent over ssl or not. + def use_ssl= use_ssl + @use_ssl = use_ssl + end + + # @return [Boolean] If this request should be sent over ssl or not. + def use_ssl? + @use_ssl + end + + # Adds a request param. + # + # @overload add_param(param_name, param_value = nil) + # Add a param (name/value) + # @param [String] param_name + # @param [String] param_value Leave blank for sub resources + # + # @overload add_param(param_obj) + # Add a param (object) + # @param [Param] param_obj + # + def add_param name_or_param, value = nil + if name_or_param.kind_of?(Param) + @params << name_or_param + else + @params << Param.new(name_or_param, value) + end + end + + # @private + def get_param param_name + @params.detect{|p| p.name == param_name } || + raise("undefined param #{param_name}") + end + + # @return [String] the request uri + def uri + querystring ? "#{path}?#{querystring}" : path + end + + # @return [String] Returns the request params url encoded, or nil if + # this request has no params. + def url_encoded_params + if @params.empty? + nil + else + @params.sort.collect{|p| p.encoded }.join('&') + end + end + + # @return [String, nil] Returns the requesty querystring. + def querystring + nil + end + + # @return [String, nil] Returns the request body. + def body + url_encoded_params + end + + # @private + class CaseInsensitiveHash < Hash + + def []= key, value + super(key.to_s.downcase, value) + end + + def [] key + super(key.to_s.downcase) + end + + def has_key?(key) + super(key.to_s.downcase) + end + alias_method :key?, :has_key? + alias_method :include?, :has_key? + alias_method :member?, :has_key? + + end + + end + end +end diff --git a/lib/aws/http/request_param.rb b/lib/aws/http/request_param.rb new file mode 100644 index 00000000000..cfbbf295b0b --- /dev/null +++ b/lib/aws/http/request_param.rb @@ -0,0 +1,63 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'cgi' + +module AWS + module Http + class Request + + # @private + class Param + + attr_accessor :name, :value + + def initialize name, value = nil + @name = name + @value = value + end + + def <=> other + @name <=> other.name + end + + def to_s + if value + "#{name}=#{value}" + else + name + end + end + + def ==(other) + other.kind_of?(Param) && + to_s == other.to_s + end + + def encoded + if value + "#{escape(name)}=#{escape(value)}" + else + escape(name) + end + end + + def escape value + CGI::escape(value.to_s).gsub('+', '%20') + end + protected :escape + + end + end + end +end diff --git a/lib/aws/http/response.rb b/lib/aws/http/response.rb new file mode 100644 index 00000000000..53d4bfc6f53 --- /dev/null +++ b/lib/aws/http/response.rb @@ -0,0 +1,75 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + + # @private + module Http + + # Represents the http response from a service request. + # + # Responses have: + # + # * status (200, 404, 500, etc) + # * headers (hash of response headers) + # * body (the response body) + # + # @private + class Response + + # @return [Integer] (200) response http status code + attr_accessor :status + + # @return [Hash] ({}) response http headers + attr_accessor :headers + + # @return [String] ('') response http body + attr_accessor :body + + # @return [String] (false) set to true if the client gives up + # before getting a response from the service. + attr_accessor :timeout + alias_method :timeout?, :timeout + + # @param [Hash] options + # @option options [Integer] :status (200) HTTP status code + # @option options [Hash] :headers ({}) HTTP response headers + # @option options [String] :body ('') HTTP response body + def initialize options = {}, &block + @status = options[:status] || 200 + @headers = options[:headers] || {} + @body = options[:body] || '' + yield(self) if block_given? + self + end + + # Returns the header value with the given name. + # + # The value is matched case-insensitively so if the headers hash + # contains a key like 'Date' and you request the value for + # 'date' the 'Date' value will be returned. + # + # @param [String,Symbol] name The name of the header to fetch a value for. + # @return [String,nil] The value of the given header + def header name + headers.each_pair do |header_name, header_value| + if header_name.downcase == name.to_s.downcase + return header_value.is_a?(Array) ? header_value.first : header_value + end + end + nil + end + + end + end +end diff --git a/lib/aws/ignore_result_element.rb b/lib/aws/ignore_result_element.rb new file mode 100644 index 00000000000..bcfc86d8443 --- /dev/null +++ b/lib/aws/ignore_result_element.rb @@ -0,0 +1,38 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/configured_xml_grammars' + +module AWS + + # @private + # + # Mixin for XML grammar modules to ignore the outer + # "OperationNameResult" element that wraps the response fields in + # Coral-like service responses. + module IgnoreResultElement + + # @private + # + # Invoked by + # ConfiguredXmlGrammars::ClassMethods#define_configured_grammars + def process_customizations(name, customizations) + [{ + "#{name}Result" => + [:ignore, *customizations] + }] + end + + end + +end diff --git a/lib/aws/indifferent_hash.rb b/lib/aws/indifferent_hash.rb new file mode 100644 index 00000000000..b661d598fc7 --- /dev/null +++ b/lib/aws/indifferent_hash.rb @@ -0,0 +1,86 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + + # A utility class to provide indifferent access to hash data. + # + # Inspired by ActiveSupport's HashWithIndifferentAccess, this class + # has a few notable differences: + # + # * ALL keys are converted to strings (via #to_s) + # * It does not deep merge/convert hashes indifferently + # * It will not perserve default value behaviours + # + # These features were omitted because our primary use for this class is to + # wrap a 1-level hash as a return value, but we want the user to access + # the values with string or symbol keys. + # + # @private + class IndifferentHash < Hash + + def initialize *args + if args.first.is_a?(Hash) + super() + merge!(*args) + else + super(*args) + end + end + + alias_method :_getter, :[] + alias_method :_setter, :[]= + + def []=(key, value) + _setter(_convert_key(key), value) + end + alias_method :store, :[]= + + def [] key + _getter(_convert_key(key)) + end + + def merge! hash + hash.each_pair do |key,value| + self[key] = value + end + self + end + alias_method :update, :merge! + + def merge hash + self.dup.merge!(hash) + end + + def has_key? key + super(_convert_key(key)) + end + alias_method :key?, :has_key? + alias_method :member?, :has_key? + alias_method :include?, :has_key? + + def fetch key, *extras, &block + super(_convert_key(key), *extras, &block) + end + + def delete key + super(_convert_key(key)) + end + + private + def _convert_key key + key.is_a?(String) ? key : key.to_s + end + + end +end diff --git a/lib/aws/inflection.rb b/lib/aws/inflection.rb new file mode 100644 index 00000000000..6fae122d191 --- /dev/null +++ b/lib/aws/inflection.rb @@ -0,0 +1,46 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + + # @private + module Inflection + + def ruby_name(aws_name) + + #aws_name.sub(/^.*:/, ''). + # gsub(/[A-Z]+[a-z]+/){|str| "_#{str.downcase}_" }. + # gsub(/(^_|_$)/, ''). + # gsub(/__/, '_'). + # downcase + + return 'etag' if aws_name == 'ETag' + + aws_name. + sub(/^.*:/, ''). # strip namespace + gsub(/([A-Z0-9]+)([A-Z][a-z])/, '\1_\2'). # split acronyms from words + scan(/[a-z]+|\d+|[A-Z0-9]+[a-z]*/). # split remaining words + join('_').downcase # join parts _ and downcase + + end + module_function :ruby_name + + def class_name(name) + name.sub(/^(.)/) { |m| m.upcase }. + gsub(/[-_]([a-z])/i) { |m| m[1,1].upcase } + end + module_function :class_name + + end + +end diff --git a/lib/aws/lazy_error_classes.rb b/lib/aws/lazy_error_classes.rb new file mode 100644 index 00000000000..f9471ff9abd --- /dev/null +++ b/lib/aws/lazy_error_classes.rb @@ -0,0 +1,64 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/xml_grammar' +require 'aws/errors' + +module AWS + + # @private + module LazyErrorClasses + + # @private + module ClassMethods + + def const_missing(name) + base_error_grammar = self::BASE_ERROR_GRAMMAR + const_missing_mutex.synchronize do + return if const_defined?(name) + const_set(name, + Class.new(self::Base) do + include Errors::ModeledError + + # so that MyService::Errors::Foo::Bar will work + const_set(:BASE_ERROR_GRAMMAR, base_error_grammar) + include LazyErrorClasses + end) + end + end + + def error_class(code) + module_eval(code.gsub(".","::")) + end + + def included(mod) + raise NotImplementedError.new("#{self} lazy-generates error classes; "+ + "therefore it is not suitable for "+ + "inclusion in other modules") + end + + end + + def self.included(mod) + unless mod.const_defined?(:BASE_ERROR_GRAMMAR) + mod.const_set(:BASE_ERROR_GRAMMAR, XmlGrammar) + end + mutex = Mutex.new + MetaUtils.extend_method(mod, :const_missing_mutex) { mutex } + mod.send(:include, Errors) + mod.extend(ClassMethods) + end + + end + +end diff --git a/lib/aws/meta_utils.rb b/lib/aws/meta_utils.rb new file mode 100644 index 00000000000..2ec4a14ad32 --- /dev/null +++ b/lib/aws/meta_utils.rb @@ -0,0 +1,43 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + + # @private + module MetaUtils + + def extend_method(object, name, &block) + object.extend( + Module.new do + define_method(name, &block) + end + ) + end + module_function :extend_method + + def class_extend_method(klass, name, &block) + klass.send(:include, + Module.new do + define_method(name, &block) + end + ) + end + module_function :class_extend_method + + def extend(object, &block) + object.extend(Module.new(&block)) + end + module_function :extend + + end +end diff --git a/lib/aws/model.rb b/lib/aws/model.rb new file mode 100644 index 00000000000..db20f4b19c6 --- /dev/null +++ b/lib/aws/model.rb @@ -0,0 +1,57 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/inflection' + +module AWS + + # @private + module Model + + # @private + def initialize(*args) + options = args.last.kind_of?(Hash) ? args.last : {} + @config = case + when options[:config] then options[:config] + when args.first.respond_to?(:config) then args.first.config + else AWS.config + end + end + + # @return [Configuration] Returns the configuration for this object. + attr_reader :config + + # Each class including this module has its own client class. + # Generally it is the service namespace suffixed by client: + # + # * s3_client + # * simple_db_client + # + # @return Retruns the proper client class for the given model. + def client + @config.send("#{config_prefix}_client") + end + + # @return [String] The short name of the service as used in coniguration. + # (e.g. SimpleDB::Client.config_prefix #=> 'simple_db') + def config_prefix + Inflection.ruby_name(self.class.to_s.split(/::/)[1]) + end + + # @return [String] A sensible default inspect string. + def inspect + "<#{self.class}>" + end + + end +end diff --git a/lib/aws/naming.rb b/lib/aws/naming.rb new file mode 100644 index 00000000000..269440b0e6c --- /dev/null +++ b/lib/aws/naming.rb @@ -0,0 +1,32 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/inflection' + +module AWS + + # @private + module Naming + + def service_name + debugger if self.name == nil + self.name.split(/::/)[1] + end + + def service_ruby_name + @service_ruby_name ||= Inflection.ruby_name(service_name) + end + + end + +end diff --git a/lib/aws/option_grammar.rb b/lib/aws/option_grammar.rb new file mode 100644 index 00000000000..c4fe283f932 --- /dev/null +++ b/lib/aws/option_grammar.rb @@ -0,0 +1,544 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/inflection' +require 'aws/meta_utils' +require 'aws/http/request_param' +require 'base64' +require 'set' + +module AWS + + # @private + class OptionGrammar + + # @private + class DefaultOption; end + + # @private + class FormatError < ArgumentError + attr_accessor :expectation + attr_accessor :context_description + + def initialize(expectation, context) + @expectation = expectation + @context_description = context + end + + def to_s + "expected #{expectation} for #{context_description}" + end + end + + # @private + module Descriptors + + # @private + module NoArgs + def apply(option) + option.extend self + end + end + + module Timestamp + + extend NoArgs + + def validate(value, context = nil) + true +# raise format_error("timestamp value", context) unless +# case value +# when String +# value =~ /^\d+$/ or value =~ /^\d{4}-\d{2}-d{2}T\d{2}:\d{2}:\d{2}Z$/ +# when String then value =~ /^2009-12-04T20:56:05.000Z\d+$/ +# when Integer then true +# when DateTime then true +# when Timestamp then true +# when Date then true +# else false +# end +# end +# value.respond_to? :to_str + end + + def encode_value(value) + value.to_s +# value.to_s +# case value +# when Integer +# when +# case value +# when nil, '' then nil +# when DateTime then raw +# when Integer then DateTime.parse(Time.at(raw).to_s) # timestamp +# else DateTime.parse(raw.to_s) # work with Time, Date and String objects +# end + end + end + + # @private + module String + + extend NoArgs + + def validate(value, context = nil) + raise format_error("string value", context) unless + value.respond_to? :to_str + end + + def encode_value(value) + value.to_s + end + + end + + # @private + module Blob + + extend NoArgs + + def validate(value, context = nil) + raise format_error("string value", context) unless + value.respond_to? :to_str + end + + def encode_value(value) + Base64.encode64(value.to_s) + end + + end + + # @private + module Integer + + extend NoArgs + + def validate(value, context = nil) + raise format_error("integer value", context) unless + value.respond_to? :to_int + end + + def encode_value(value) + value.to_s + end + + end + + # @private + module Boolean + + extend NoArgs + + def validate(value, context = nil) + raise format_error("boolean value", context) unless + value == true || value == false + end + + def encode_value(value) + value.to_s + end + + end + + # @private + module Required + extend NoArgs + def required?; true; end + end + + # @private + module Rename + def self.apply(option, new_name) + new_name = Inflection.ruby_name(new_name) + MetaUtils.extend_method(option, :ruby_name) { new_name } + end + end + + # @private + module ListMethods + + module ClassMethods + + def apply(option, member_descriptors) + super(option) + member_option = option.member_option if option.respond_to?(:member_option) + member_option ||= ListMember.new + member_option = member_option.extend_with_config(*member_descriptors) + MetaUtils.extend_method(option, :member_option) { member_option } + end + + end + + module InstanceMethods + + def validate(value, context = nil) + raise format_error("enumerable value", context) unless + value.respond_to? :each + i = 0 + value.each do |member| + i += 1 + member_option.validate(member, + "member #{i} of #{context_description(context)}") + end + end + + def request_params(value, prefix = nil) + params = [] + value.each do |v| + name = prefixed_name(prefix) + join + (params.size + 1).to_s + params << member_option.request_params(v, name) + end + return [Http::Request::Param.new(prefixed_name(prefix), "")] if params.empty? + params + end + + def join + '.' + end + + end + + end + + module List + + extend NoArgs + extend ListMethods::ClassMethods + include ListMethods::InstanceMethods + + end + + module MemberedList + + extend NoArgs + extend ListMethods::ClassMethods + include ListMethods::InstanceMethods + + def join + '.member.' + end + + end + + class ListMember < DefaultOption + + def initialize options = {} + super("##list-member##") + @prefix = options[:prefix] || '' + end + + def prefixed_name(prefix) + "#{prefix}#{@prefix}" + end + + end + + # @private + module Structure + + extend NoArgs + + def self.apply(option, members) + options = {} + options = option.member_options.inject({}) do |memo, member_option| + memo[member_option.name] = member_option + memo + end if option.respond_to?(:member_options) + + super(option) + + members.each do |(name, descriptors)| + member_option = options[name] || DefaultOption.new(name) + member_option = member_option.extend_with_config(*descriptors) + options[name] = member_option + end + + MetaUtils.extend_method(option, :member_options) { options.values } + by_ruby_name = options.values.inject({}) do |memo, member_option| + memo[member_option.ruby_name] = member_option + memo + end + MetaUtils.extend_method(option, :member_option) { |n| by_ruby_name[n] } + end + + def validate(value, context = nil) + raise format_error("hash value", context) unless + value.respond_to?(:to_hash) + + context = context_description(context) + + value.each do |name, v| + name = name.to_s + raise ArgumentError.new("unexpected key #{name} for #{context}") unless + member_option(name) + member_option(name).validate(v, "key #{name} of #{context}") + end + + member_options.each do |option| + raise ArgumentError.new("missing required key #{option.ruby_name} for #{context}") if + option.required? and + !value.has_key?(option.ruby_name) and + !value.has_key?(option.ruby_name.to_sym) + end + end + + def request_params(values, prefix = nil) + values.map do |name, value| + name = name.to_s + member_option(name).request_params(value, prefixed_name(prefix)) + end.flatten + end + + end + + # @private + module Boolean + extend NoArgs + end + + end + + class DefaultOption + + attr_reader :name + + def initialize(name) + @name = name + end + + def ruby_name + Inflection.ruby_name(name) + end + + def request_params(value, prefix = nil) + [Http::Request::Param.new(prefixed_name(prefix), encode_value(value))] + end + + def prefixed_name(prefix) + return "#{prefix}.#{name}" if prefix + name + end + + def encode_value(value) + value + end + + def required?; false; end + + def format_error(expected, context = nil) + context = context_description(context) + FormatError.new(expected, context) + end + + def context_description(context) + context or "option #{ruby_name}" + end + + def extend_with_config(*descriptors) + option = clone + descriptors.each do |desc| + if desc.kind_of?(Hash) + (name, arg) = desc.to_a.first + else + name = desc + arg = nil + end + class_name = Inflection.class_name(name.to_s) + mod = Descriptors::const_get(class_name) + if arg + mod.apply(option, arg) + else + mod.apply(option) + end + end + option + end + + include Descriptors::String + + end + + # @private + module ModuleMethods + + include Inflection + + def customize(config = []) + m = Class.new(self) + supported_options = m.supported_options.inject({}) do |memo, opt| + memo[opt.name] = opt + memo + end + config.each do |option_config| + if config.kind_of?(Hash) + (name, value_desc) = option_config + else + (name, value_desc) = parse_option(option_config) + end + option = supported_options[name] || DefaultOption.new(name) + option = option.extend_with_config(*value_desc) + supported_options[option.name] = option + end + + supported_ary = supported_options.values + MetaUtils.extend_method(m, :supported_options) { supported_ary } + supported_ruby_names = supported_ary.inject({}) do |memo, opt| + memo[opt.ruby_name] = opt + memo + end + MetaUtils.extend_method(m, :option) { |n| supported_ruby_names[n] } + supported_ary.each do |opt| + MetaUtils.extend_method(m, "validate_#{opt.ruby_name}") do |value| + opt.validate(value) + end + end + + m + end + + def option(name) + nil + end + + def supported_options + [] + end + + def validate(options) + options.each do |name, value| + name = name.to_s + raise ArgumentError.new("unexpected option #{name}") unless + option(name) + option(name).validate(value) + end + supported_options.each do |option| + raise ArgumentError.new("missing required option #{option.ruby_name}") unless + !option.required? || + options.has_key?(option.ruby_name) || options.has_key?(option.ruby_name.to_sym) + end + end + + def request_params(options) + validate(options) + options.map do |(name, value)| + name = name.to_s + option(name).request_params(value) + end.flatten + end + + def included(m) + m.extend(self::ModuleMethods) + end + + protected + def parse_option(option) + value_desc = nil + if option.kind_of? Hash + raise ArgumentError.new("passed empty hash where an option was expected") if + option.empty? + + raise ArgumentError.new("too many entries in option description") if + option.size > 1 + + (name, value_desc) = option.to_a.first + name = name.to_s + + raise ArgumentError.new("expected an array for "+ + "value description of option #{name},"+ + "got #{value_desc.inspect}") unless + value_desc.nil? or value_desc.kind_of?(Array) + else + name = option + end + + value_desc ||= [] + + [name, value_desc] + end + + protected + def apply_required_descriptor(m, name) + name = ruby_name(name) + MetaUtils.extend_method(m, :validate) do |opts| + raise ArgumentError.new("missing required option #{name}") unless + opts.key? name or opts.key? name.to_sym + end + end + + protected + def apply_integer_descriptor(m, name) + MetaUtils.extend_method(m, "validate_#{ruby_name(name)}") do |value| + raise ArgumentError.new("expected integer value for option #{ruby_name(name)}") unless + value.respond_to? :to_int + end + end + + protected + def apply_string_descriptor(m, name) + MetaUtils.extend_method(m, "validate_#{ruby_name(name)}") do |value| + raise ArgumentError.new("expected string value for option #{ruby_name(name)}") unless + value.respond_to? :to_str + end + end + + protected + def apply_list_descriptor(m, name, arg) + MetaUtils.extend_method(m, "validate_#{ruby_name(name)}") do |value| + raise ArgumentError.new("expected value for option #{ruby_name(name)} "+ + "to respond to #each") unless + value.respond_to? :each + end + MetaUtils.extend_method(m, "params_for_#{ruby_name(name)}") do |value| + i = 0 + values = [] + value.each do |member| + i += 1 + values << Http::Request::Param.new(name+"."+i.to_s, member.to_s) + end + if i > 0 + values + else + Http::Request::Param.new(name, "") + end + end + end + + protected + def apply_rename_descriptor(m, name, new_name) + name = ruby_name(name) + MetaUtils.extend_method(m, :validate) do |opts| + raise ArgumentError.new("unexpected option foo") if + opts.key?(name) or opts.key?(name.to_sym) + + opts = opts.dup + opts[name] = opts[new_name] if opts.key?(new_name) + opts[name.to_sym] = opts[new_name.to_sym] if opts.key?(new_name.to_sym) + opts.delete(new_name) + opts.delete(new_name.to_sym) + super(opts) + end + + # couldn't find a better way to alias a class method + method = m.method("params_for_#{name}") + MetaUtils.extend_method(m, "params_for_#{new_name}") do |value| + method.call(value) + end + end + + end + + extend ModuleMethods + + end + +end diff --git a/lib/aws/policy.rb b/lib/aws/policy.rb new file mode 100644 index 00000000000..03a57380686 --- /dev/null +++ b/lib/aws/policy.rb @@ -0,0 +1,912 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/inflection' +require 'uuidtools' +require 'date' + +module AWS + + # Represents an access policy for S3 operations and resources. For example: + # + # policy = Policy.new do |policy| + # policy.allow(:actions => ['s3:PutObject'], + # :resources => "arn:aws:s3:::mybucket/mykey/*", + # :principals => :any + # ).where(:acl).is("public-read") + # end + # + # policy.to_json # => '{ "Version":"2008-10-17", ...' + # + # @see #initialize More ways to construct a policy. + # @see http://docs.amazonwebservices.com/AmazonS3/latest/dev/AccessPolicyLanguage_UseCases_s3_a.html Example policies (in JSON). + class Policy + + # @see Statement + # @return [Array] An array of policy statements. + attr_reader :statements + + # @return [String] The version of the policy language used in this + # policy object. + attr_reader :version + + # @return [String] A unique ID for the policy. + attr_reader :id + + class Statement; end + + # Constructs a policy. There are a few different ways to + # build a policy: + # + # * With hash arguments: + # + # Policy.new(:statements => [ + # { :effect => :allow, + # :actions => :all, + # :principals => ["abc123"], + # :resources => "mybucket/mykey" + # } + # ]) + # + # * From a JSON policy document: + # + # Policy.from_json(policy_json_string) + # + # * With a block: + # + # Policy.new do |policy| + # + # policy.allow( + # :actions => ['s3:PutObject'], + # :resources => "arn:aws:s3:::mybucket/mykey/*", + # :principals => :any + # ).where(:acl).is("public-read") + # + # end + # + def initialize(opts = {}) + @statements = opts.values_at(:statements, "Statement").select do |a| + a.kind_of?(Array) + end.flatten.map do |stmt| + self.class::Statement.new(stmt) + end + + if opts.has_key?(:id) or opts.has_key?("Id") + @id = opts[:id] || opts["Id"] + else + @id = UUIDTools::UUID.timestamp_create.to_s + end + if opts.has_key?(:version) or opts.has_key?("Version") + @version = opts[:version] || opts["Version"] + else + @version = "2008-10-17" + end + + yield(self) if block_given? + end + + # @return [Boolean] Returns true if the two policies are the same. + def ==(other) + if other.kind_of?(AWS::Policy) + self.hash_without_ids == other.hash_without_ids + else + false + end + end + alias_method :eql?, :== + + # Removes the ids from the policy and its statements for the purpose + # of comparing two policies for equivilence. + # @return [Hash] Returns the policy as a hash with no ids + # @private + def hash_without_ids + hash = self.to_h + hash.delete('Id') + hash['Statement'].each do |statement| + statement.delete('Sid') + end + hash + end + protected :hash_without_ids + + # Returns a hash representation of the policy; the + # following statements are equivalent: + # + # policy.to_h.to_json + # policy.to_json + # + # @return [Hash] + def to_h + { + "Version" => version, + "Id" => id, + "Statement" => statements.map { |st| st.to_h } + } + end + + # @return [String] a JSON representation of the policy. + def to_json + to_h.to_json + end + + # Constructs a policy from a JSON representation. + # @see #initialize + # @return [Policy] Returns a Policy object constructed by parsing + # the passed JSON policy. + def self.from_json(json) + new(JSON.parse(json)) + end + + # Convenient syntax for expressing operators in statement + # condition blocks. For example, the following: + # + # policy.allow.where(:s3_prefix).not("forbidden"). + # where(:current_time).lte(Date.today+1) + # + # is equivalent to: + # + # conditions = Policy::ConditionBlock.new + # conditions.add(:not, :s3_prefix, "forbidden") + # conditions.add(:lte, :current_time, Date.today+1) + # policy.allow(:conditions => conditions) + # + # @see ConditionBlock#add + class OperatorBuilder + + # @private + def initialize(condition_builder, key) + @condition_builder = condition_builder + @key = key + end + + def method_missing(m, *values) + @condition_builder.conditions.add(m, @key, *values) + @condition_builder + end + + end + + # Convenient syntax for adding conditions to a statement. + # @see Policy#allow + # @see Policy#deny + class ConditionBuilder + + # @return [Array] Returns an array of policy conditions. + attr_reader :conditions + + # @private + def initialize(conditions) + @conditions = conditions + end + + # Adds a condition for the given key. For example: + # + # policy.allow(...).where(:current_time).lte(Date.today + 1) + # + # @return [OperatorBuilder] + def where(key, operator = nil, *values) + if operator + @conditions.add(operator, key, *values) + self + else + OperatorBuilder.new(self, key) + end + end + + end + + # Convenience method for constructing a new statement with the + # "Allow" effect and adding it to the policy. For example: + # + # policy.allow(:actions => [:put_object], + # :principals => :any, + # :resources => "mybucket/mykey/*"). + # where(:acl).is("public-read") + # + # @option (see Statement#initialize) + # @see Statement#initialize + # @return [ConditionBuilder] + def allow(opts = {}) + stmt = self.class::Statement.new(opts.merge(:effect => :allow)) + statements << stmt + ConditionBuilder.new(stmt.conditions) + end + + # Convenience method for constructing a new statement with the + # "Deny" effect and adding it to the policy. For example: + # + # policy.deny( + # :actions => [:put_object], + # :principals => :any, + # :resources => "mybucket/mykey/*" + # ).where(:acl).is("public-read") + # + # @param (see Statement#initialize) + # @see Statement#initialize + # @return [ConditionBuilder] + def deny(opts = {}) + stmt = self.class::Statement.new(opts.merge(:effect => :deny)) + statements << stmt + ConditionBuilder.new(stmt.conditions) + end + + # Represents the condition block of a policy. In JSON, + # condition blocks look like this: + # + # { "StringLike": { "s3:prefix": ["photos/*", "photos.html"] } } + # + # ConditionBlock lets you specify conditions like the above + # example using the add method, for example: + # + # conditions.add(:like, :s3_prefix, "photos/*", "photos.html") + # + # See the add method documentation for more details about how + # to specify keys and operators. + # + # This class also provides a convenient way to query a + # condition block to see what operators, keys, and values it + # has. For example, consider the following condition block + # (in JSON): + # + # { + # "StringEquals": { + # "s3:prefix": "photos/index.html" + # }, + # "DateEquals": { + # "aws:CurrentTime": ["2010-10-12", "2011-01-02"] + # }, + # "NumericEquals": { + # "s3:max-keys": 10 + # } + # } + # + # You can get access to the condition data using #[], #keys, + # #operators, and #values -- for example: + # + # conditions["DateEquals"]["aws:CurrentTime"].values + # # => ["2010-10-12", "2011-01-02"] + # + # You can also perform more sophisticated queries, like this + # one: + # + # conditions[:is].each do |equality_conditions| + # equality_conditions.keys.each do |key| + # puts("#{key} may be any of: " + + # equality_conditions[key].values.join(" ") + # end + # end + # + # This would print the following lines: + # + # s3:prefix may be any of: photos/index.html + # aws:CurrentTime may be any of: 2010-10-12 2011-01-02 + # s3:max-keys may be any of: 10 + # + class ConditionBlock + + # @private + def initialize(conditions = {}) + # filter makes a copy + @conditions = filter_conditions(conditions) + end + + # Adds a condition to the block. This method defines a + # convenient set of abbreviations for operators based on the + # type of value passed in. For example: + # + # conditions.add(:is, :secure_transport, true) + # + # Maps to: + # + # { "Bool": { "aws:SecureTransport": true } } + # + # While: + # + # conditions.add(:is, :s3_prefix, "photos/") + # + # Maps to: + # + # { "StringEquals": { "s3:prefix": "photos/" } } + # + # The following list shows which operators are accepted as + # symbols and how they are represented in the JSON policy: + # + # * +:is+ (StringEquals, NumericEquals, DateEquals, or Bool) + # * +:like+ (StringLike) + # * +:not_like+ (StringNotLike) + # * +:not+ (StringNotEquals, NumericNotEquals, or DateNotEquals) + # * +:greater_than+, +:gt+ (NumericGreaterThan or DateGreaterThan) + # * +:greater_than_equals+, +:gte+ + # (NumericGreaterThanEquals or DateGreaterThanEquals) + # * +:less_than+, +:lt+ (NumericLessThan or DateLessThan) + # * +:less_than_equals+, +:lte+ + # (NumericLessThanEquals or DateLessThanEquals) + # * +:is_ip_address+ (IpAddress) + # * +:not_ip_address+ (NotIpAddress) + # * +:is_arn+ (ArnEquals) + # * +:not_arn+ (ArnNotEquals) + # * +:is_arn_like+ (ArnLike) + # * +:not_arn_like+ (ArnNotLike) + # + # @param [Symbol or String] operator The operator used to + # compare the key with the value. See above for valid + # values and their interpretations. + # + # @param [Symbol or String] key The key to compare. Symbol + # keys are inflected to match AWS conventions. By + # default, the key is assumed to be in the "aws" + # namespace, but if you prefix the symbol name with "s3_" + # it will be sent in the "s3" namespace. For example, + # +:s3_prefix+ is sent as "s3:prefix" while + # +:secure_transport+ is sent as "aws:SecureTransport". + # See + # http://docs.amazonwebservices.com/AmazonS3/latest/dev/UsingResOpsConditions.html + # for a list of the available keys for each action in S3. + # + # @param value The value to compare against. + # This can be: + # * a String + # * a number + # * a Date, DateTime, or Time + # * a boolean value + # This method does not attempt to validate that the values + # are valid for the operators or keys they are used with. + def add(operator, key, *values) + if operator.kind_of?(Symbol) + converted_values = values.map { |v| convert_value(v) } + else + converted_values = values + end + operator = translate_operator(operator, values.first) + op = (@conditions[operator] ||= {}) + raise "duplicate #{operator} conditions for #{key}" if op[key] + op[translate_key(key)] = converted_values + end + + # @private + def to_h + @conditions + end + + # Filters the conditions described in the block, returning a + # new ConditionBlock that contains only the matching + # conditions. Each argument is matched against either the + # keys or the operators in the block, and you can specify + # the key or operator in any way that's valid for the #add + # method. Some examples: + # + # # all conditions using the StringLike operator + # conditions["StringLike"] + # + # # all conditions using StringEquals, DateEquals, NumericEquals, or Bool + # conditions[:is] + # + # # all conditions on the s3:prefix key + # conditions["s3:prefix"] + # + # # all conditions on the aws:CurrentTime key + # conditions[:current_time] + # + # Multiple conditions are ANDed together, so the following + # are equivalent: + # + # conditions[:s3_prefix][:is] + # conditions[:is][:s3_prefix] + # conditions[:s3_prefix, :is] + # + # @see #add + # @return [ConditionBlock] A new set of conditions filtered by the + # given conditions. + def [](*args) + filtered = @conditions + args.each do |filter| + type = valid_operator?(filter) ? nil : :key + filtered = filter_conditions(filtered) do |op, key, value| + (match, type) = match_triple(filter, type, op, key, value) + match + end + end + self.class.new(filtered) + end + + # @return [Array] Returns an array of operators used in this block. + def operators + @conditions.keys + end + + # @return [Array] Returns an array of unique keys used in the block. + def keys + @conditions.values.map do |keys| + keys.keys if keys + end.compact.flatten.uniq + end + + # Returns all values used in the block. Note that the + # values may not all be from the same condition; for example: + # + # conditions.add(:like, :user_agent, "mozilla", "explorer") + # conditions.add(:lt, :s3_max_keys, 12) + # conditions.values # => ["mozilla", "explorer", 12] + # + # @return [Array] Returns an array of values used in this condition block. + def values + @conditions.values.map do |keys| + keys.values + end.compact.flatten + end + + # @private + protected + def match_triple(filter, type, op, key, value) + value = [value].flatten.first + if type + target = (type == :operator ? op : key) + match = send("match_#{type}", filter, target, value) + else + if match_operator(filter, op, value) + match = true + type = :operator + elsif match_key(filter, key) + match = true + type = :key + else + match = false + end + end + [match, type] + end + + # @private + protected + def match_operator(filter, op, value) + # dates are the only values that don't come back as native types in JSON + # but where we use the type as a cue to the operator translation + value = Date.today if op =~ /^Date/ + translate_operator(filter, value) == op + end + + # @private + protected + def match_key(filter, key, value = nil) + translate_key(filter) == key + end + + # @private + protected + def filter_conditions(conditions = @conditions) + conditions.inject({}) do |m, (op, keys)| + m[op] = keys.inject({}) do |m2, (key, value)| + m2[key] = value if !block_given? or yield(op, key, value) + m2 + end + m.delete(op) if m[op].empty? + m + end + end + + # @private + protected + def translate_key(key) + if key.kind_of?(Symbol) + if key.to_s =~ /^s3_(.*)$/ + s3_name = $1 + if s3_name == "version_id" or + s3_name == "location_constraint" + s3_name = Inflection.class_name(s3_name) + else + s3_name.tr!('_', '-') + end + "s3:#{s3_name}" + else + "aws:#{Inflection.class_name(key.to_s)}" + end + else + key + end + end + + # @private + MODIFIERS = { + /_ignoring_case$/ => "IgnoreCase", + /_equals$/ => "Equals" + } + + # @private + protected + def valid_operator?(operator) + translate_operator(operator, "") + true + rescue ArgumentError => e + false + end + + # @private + protected + def translate_operator(operator, example_value) + return operator if operator.kind_of?(String) + + original_operator = operator + (operator, opts) = strip_modifiers(operator) + + raise ArgumentError.new("unrecognized operator #{original_operator}") unless + respond_to?("translate_#{operator}", true) + send("translate_#{operator}", example_value, opts) + end + + # @private + protected + def translate_is(example, opts) + return "Bool" if type_notation(example) == "Bool" + base_translate(example, "Equals", opts[:ignore_case]) + end + + # @private + protected + def translate_not(example, opts) + base_translate(example, "NotEquals", opts[:ignore_case]) + end + + # @private + protected + def translate_like(example, opts) + base_translate(example, "Like") + end + + # @private + protected + def translate_not_like(example, opts) + base_translate(example, "NotLike") + end + + # @private + protected + def translate_less_than(example, opts) + base_translate(example, "LessThan", opts[:equals]) + end + alias_method :translate_lt, :translate_less_than + + # @private + protected + def translate_lte(example, opts) + translate_less_than(example, { :equals => "Equals" }) + end + + # @private + protected + def translate_greater_than(example, opts) + base_translate(example, "GreaterThan", opts[:equals]) + end + alias_method :translate_gt, :translate_greater_than + + # @private + protected + def translate_gte(example, opts) + translate_greater_than(example, { :equals => "Equals" }) + end + + # @private + protected + def translate_is_ip_address(example, opts) + "IpAddress" + end + + # @private + protected + def translate_not_ip_address(example, opts) + "NotIpAddress" + end + + # @private + protected + def translate_is_arn(example, opts) + "ArnEquals" + end + + # @private + protected + def translate_not_arn(example, opts) + "ArnNotEquals" + end + + # @private + protected + def translate_is_arn_like(example, opts) + "ArnLike" + end + + # @private + protected + def translate_not_arn_like(example, opts) + "ArnNotLike" + end + + # @private + protected + def base_translate(example, base_operator, *modifiers) + "#{type_notation(example)}#{base_operator}#{modifiers.join}" + end + + # @private + protected + def type_notation(example) + case example + when String + "String" + when Numeric + "Numeric" + when Time, Date + "Date" + when true, false + "Bool" + end + end + + # @private + protected + def convert_value(value) + case value + when DateTime, Time + Time.parse(value.to_s).iso8601 + when Date + value.strftime("%Y-%m-%d") + else + value + end + end + + # @private + protected + def strip_modifiers(operator) + opts = {} + MODIFIERS.each do |(regex, mod)| + ruby_name = Inflection.ruby_name(mod).to_sym + opts[ruby_name] = "" + if operator.to_s =~ regex + opts[ruby_name] = mod + operator = operator.to_s.sub(regex, '').to_sym + end + end + [operator, opts] + end + + end + + # Represents a statement in a policy. + # + # @see Policy#allow + # @see Policy#deny + class Statement + + # @return [String] Returns the statement id + attr_accessor :sid + + # @return [String] Returns the statement effect, either "Allow" or + # "Deny" + attr_accessor :effect + + # @return [Array] Returns an array of principals. + attr_accessor :principals + + # @return [Array] Returns an array of statement actions included + # by this policy statement. + attr_accessor :actions + + # @return [Array] Returns an array of actions excluded by this + # policy statement. + attr_accessor :excluded_actions + + # @return [Array] Returns an array of resources affected by this + # policy statement. + attr_accessor :resources + + # @return [Array] Returns an array of conditions for this policy. + attr_accessor :conditions + + # Constructs a new statement. + # + # @option opts [String] :sid The statement ID. This is optional; if + # omitted, a UUID will be generated for the statement. + # @option opts [String] :effect The statement effect, which must be either + # "Allow" or "Deny". + # @see Policy#allow + # @see Policy#deny + # @option opts [String or array of strings] :principals The account(s) + # affected by the statement. These should be AWS account IDs. + # @option opts :actions The action or actions affected by + # the statement. These can be symbols or strings. If + # they are strings, you can use wildcard character "*" + # to match zero or more characters in the action name. + # Symbols are expected to match methods of S3::Client. + # @option opts :excluded_actions Action or actions which are + # explicitly not affected by this statement. As with + # +:actions+, these may be symbols or strings. + # @option opts [String or array of strings] :resources The + # resource(s) affected by the statement. These can be + # expressed as ARNs (e.g. +arn:aws:s3:::mybucket/mykey+) + # or you may omit the +arn:aws:s3:::+ prefix and just give + # the path as +bucket_name/key+. You may use the wildcard + # character "*" to match zero or more characters in the + # resource name. + # @option opts [ConditionBlock or Hash] :conditions + # Additional conditions that narrow the effect of the + # statement. It's typically more convenient to use the + # ConditionBuilder instance returned from Policy#allow or + # Policy#deny to add conditions to a statement. + # @see S3::Client + def initialize(opts = {}) + self.sid = UUIDTools::UUID.timestamp_create.to_s + self.conditions = ConditionBlock.new + + parse_options(opts) + + yield(self) if block_given? + end + + # Convenience method to add to the list of actions affected + # by this statement. + def include_actions(*actions) + self.actions ||= [] + self.actions.push(*actions) + end + alias_method :include_action, :include_actions + + # Convenience method to add to the list of actions + # explicitly not affected by this statement. + def exclude_actions(*actions) + self.excluded_actions ||= [] + self.excluded_actions.push(*actions) + end + alias_method :exclude_action, :exclude_actions + + # @private + def to_h + stmt = { + "Sid" => sid, + "Effect" => Inflection.class_name(effect.to_s), + "Principal" => principals_hash, + "Resource" => resource_arns, + "Condition" => (conditions.to_h if conditions) + } + if !translated_actions || translated_actions.empty? + stmt["NotAction"] = translated_excluded_actions + else + stmt["Action"] = translated_actions + end + stmt + end + + protected + def parse_options(options) + options.each do |name, value| + name = Inflection.ruby_name(name.to_s) + name.sub!(/s$/,'') + send("parse_#{name}_option", value) if + respond_to?("parse_#{name}_option", true) + end + end + + protected + def parse_effect_option(value) + self.effect = value + end + + protected + def parse_sid_option(value) + self.sid = value + end + + protected + def parse_action_option(value) + coerce_array_option(:actions, value) + end + + protected + def parse_not_action_option(value) + coerce_array_option(:excluded_actions, value) + end + alias_method :parse_excluded_action_option, :parse_not_action_option + + protected + def parse_principal_option(value) + if value and value.kind_of?(Hash) + value = value["AWS"] || [] + end + + coerce_array_option(:principals, value) + end + + protected + def parse_resource_option(value) + coerce_array_option(:resources, value) + end + + protected + def parse_condition_option(value) + self.conditions = ConditionBlock.new(value) + end + + protected + def coerce_array_option(attr, value) + if value.kind_of?(Array) + send("#{attr}=", value) + else + send("#{attr}=", [value]) + end + end + + protected + def principals_hash + return nil unless principals + { "AWS" => + principals.map do |principal| + principal == :any ? "*" : principal + end } + end + + protected + def translate_action(action) + case action + when String then action + when :any then '*' + when Symbol + + if self.class == AWS::Policy::Statement + msg = 'symbolized action names are only accepted by service ' + + 'specific policies (e.g. AWS::S3::Policy)' + raise ArgumentError, msg + end + + unless self.class::ACTION_MAPPING.has_key?(action) + raise ArgumentError, "unrecognized action: #{action}" + end + + self.class::ACTION_MAPPING[action] + + end + end + + protected + def translated_actions + return nil unless actions + actions.map do |action| + translate_action(action) + end + end + + protected + def translated_excluded_actions + return nil unless excluded_actions + excluded_actions.map { |a| translate_action(a) } + end + + protected + def resource_arns + return nil unless resources + resources.map do |resource| + case resource + when :any then "*" + else resource_arn(resource) + end + end + end + + protected + def resource_arn resource + resource.to_s + end + + end + + end + +end diff --git a/lib/aws/rails.rb b/lib/aws/rails.rb new file mode 100644 index 00000000000..e51870e5287 --- /dev/null +++ b/lib/aws/rails.rb @@ -0,0 +1,209 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws' + +module AWS + + if Object.const_defined?(:Rails) and Rails.const_defined?(:Railtie) + + class Railtie < Rails::Railtie + + # configure our plugin on boot. other extension points such + # as configuration, rake tasks, etc, are also available + initializer "aws-sdk.initialize" do |app| + AWS::Rails.setup + end + end + + end + + # A handful of useful Rails integration methods. + # + # If you require this gem inside a Rails application (via config.gem + # for rails 2 and bundler for rails 3) then AWS::Rails.setup + # is called automatically. + module Rails + + # Adds extra functionality to Rails. + # + # Normailly this method is invoked automatically when you require this + # gem in a Rails Application: + # + # Rails 3+ (RAILS_ROOT/Gemfile) + # + # gem 'aws-sdk-rails' + # + # Rails 2.1 - 2.3 (RAILS_ROOT/config/environment.rb) + # + # config.gem 'aws-sdk-rails' + # + # === Selective Rails Features + # + # If you would prefer to cherry pick a few of the features added by this + # gem you can change your gem requirement to load 'aws/rails' instead: + # + # Rails 3+ (RAILS_ROOT/Gemfile) + # + # gem 'aws-sdk-rails', :require => 'aws/rails' + # + # Rails 2.1 - 2.3 (RAILS_ROOT/config/environment.rb) + # + # config.gem 'aws-sdk-rails', :lib => 'aws/rails' + # + # In both of the examples above you can now configure AWS and call the setup + # methods of choice inside a config initializer (e.g. + # RAILS_ROOT/config/initializers/aws.rb): + # + # AWS.config(...) + # AWS.log_to_rails_logger + # AWS.add_action_mailer_delivery_method + # + # @return [nil] + def self.setup + load_yaml_config + add_action_mailer_delivery_method + log_to_rails_logger + nil + end + + # Loads AWS configuration options from +RAILS_ROOT/config/aws.yml+. + # + # This configuration file is optional. You can omit this file and instead + # use ruby to configure AWS inside a configuration initialization script + # (e.g. RAILS_ROOT/config/intializers/aws.rb). + # + # If you have a yaml configuration file it should be formatted like the + # standard +database.yml+ file in a Rails application. This means there + # should be one section for Rails environment: + # + # development: + # access_key_id: YOUR_ACCESS_KEY_ID + # secret_access_key: YOUR_SECRET_ACCESS_KEY + # simple_db_consistent_reads: false + # + # production: + # access_key_id: YOUR_ACCESS_KEY_ID + # secret_access_key: YOUR_SECRET_ACCESS_KEY + # simple_db_consistent_reads: true + # + # You should also consider DRYing up your configuration file using + # YAML references: + # + # development: + # access_key_id: YOUR_ACCESS_KEY_ID + # secret_access_key: YOUR_SECRET_ACCESS_KEY + # simple_db_consistent_reads: false + # + # production: + # <<: *development + # simple_db_consistent_reads: true + # + # The yaml file will also be ERB parsed so you can use ruby inside of it: + # + # development: + # access_key_id: YOUR_ACCESS_KEY_ID + # secret_access_key: <%= read_secret_from_a_secure_location %> + # simple_db_consistent_reads: false + # + # production: + # <<: *development + # simple_db_consistent_reads: true + # + def self.load_yaml_config + + path = Pathname.new("#{rails_root}/config/aws.yml") + + if File.exists?(path) + cfg = YAML::load(ERB.new(File.read(path)).result)[rails_env] + AWS.config(cfg) + end + + end + + # Adds a delivery method to ActionMailer that uses {AWS::SimplEmailService}. + # + # Once you have an SES delivery method you can configure Rails to use this + # for ActionMailer in your environment configuration (e.g. + # RAILS_ROOT/config/environments/production.rb) + # + # config.action_mailer.delivery_method = :amazon_ses + # + # === Defaults + # + # Normally you don't need to call this method. By default a delivery method + # named +:amazon_ses+ is added to ActionMailer::Base. This delivery method + # uses your default configuration (#{AWS.config}). + # + # === Custom SES Options + # + # If you need to supply configuration values for SES that are different than + # those in {AWS.config} then you can pass those options: + # + # AWS.add_action_mailer_delivery_method(:ses, custom_options) + # + # @param [Hash] options + # @param [Symbol] name (:amazon_ses) The name of the delivery + # method. The name used here should be the same as you set in your + # environment config. If you name the delivery method +:amazon_ses+ then + # you could do something like this in your config/environments/ENV.rb file: + # + # config.action_mailer.delivery_method = :amazon_ses + # + # @param [Hash] options ({}) A hash of options that are passes to + # {AWS::SimpleEmailService#new} before delivering email. + # + # @return [nil] + def self.add_action_mailer_delivery_method name = :amazon_ses, options = {} + + amb = ::ActionMailer::Base + + if ::Rails.version.to_f >= 3 + amb.add_delivery_method(name, AWS::SimpleEmailService, options) + else + amb.send(:define_method, "perform_delivery_#{name}") do |mail| + AWS::SimpleEmailService.new(options).send_raw_email(mail) + end + end + + nil + + end + + # Configures AWS to log to the Rails defualt logger. + # @return [nil] + def self.log_to_rails_logger + AWS.config(:logger => rails_logger) + nil + end + + # @private + protected + def self.rails_env + ::Rails.respond_to?(:env) ? ::Rails.env : RAILS_ENV + end + + # @private + protected + def self.rails_root + ::Rails.respond_to?(:root) ? ::Rails.root.to_s : RAILS_ROOT + end + + # @private + protected + def self.rails_logger + ::Rails.respond_to?(:logger) ? ::Rails.logger : ::RAILS_DEFAULT_LOGGER + end + + end +end diff --git a/lib/aws/record.rb b/lib/aws/record.rb new file mode 100644 index 00000000000..03d3e9211c0 --- /dev/null +++ b/lib/aws/record.rb @@ -0,0 +1,79 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'set' +require 'aws/record/base' + +module AWS + module Record + + # @private + class RecordNotFound < Exception; end + + # Sets a prefix to be applied to all SimpleDB domains associated with + # AWS::Record::Base classes. + # + # AWS::Record.domain_prefix = 'production_' + # + # class Product < AWS::Record::Base + # set_domain_name 'products' + # end + # + # Product.domain_name #=> 'production_products' + # + # @param [String] A prefix to append to all domains. This is useful for + # grouping domains used by one application with a single prefix. + def self.domain_prefix= prefix + @prefix = prefix + end + + # @return [String,nil] The string that is prepended to all domain names. + def self.domain_prefix + @prefix + end + + # A utility method for casting values into an array. + # + # * nil is returned as an empty array, [] + # * Arrays are returned unmodified + # * Everything else is returned as the sole element of an array + # + # @param [Object] value + # @return [Array] The value cast into an array + # @private + def self.as_array value + case value + when nil then [] + when Set then value.to_a + when Array then value + else [value] + end + end + + # A utility method for casting values into + # + # * Sets are returned unmodified + # * everything else is passed through #{as_array} and then into a new Set + # + # @param [Object] value + # @return [Set] The value cast into a Set. + # @private + def self.as_set value + case value + when Set then value + else Set.new(as_array(value)) + end + end + + end +end diff --git a/lib/aws/record/attribute.rb b/lib/aws/record/attribute.rb new file mode 100644 index 00000000000..22e663851b9 --- /dev/null +++ b/lib/aws/record/attribute.rb @@ -0,0 +1,94 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + module Record + + # Base class for all of the AWS::Record attributes. + # @private + class Attribute + + # @param [Symbol] Name of this attribute. It should be a name that + # is safe to use as a method. + # @param [Hash] options + # @option options [Boolean] :set (false) When true this attribute can + # accept multiple unique values. + def initialize name, options = {} + @name = name.to_s + @options = options.dup + if options[:set] and !self.class.allow_set? + raise ArgumentError, "invalid option :set for #{self.class}" + end + end + + # @return [String] The name of this attribute + attr_reader :name + + # @return [Hash] The hash of options this attribute was constructed with + attr_reader :options + + # @return [Boolean] Returns true if this attribute can have + # multiple values. + def set? + options[:set] ? true : false + end + + # @return Returns the default value for this attribute. Defaults to nil. + def default_value + options[:default_value] + end + + # @param [Mixed] A single value to type cast. + # @return [Mixed] Returns the type casted value. + def type_cast raw_value + self.class.type_cast(raw_value, options) + end + + # @param [String] The serialized string value. + # @return [Mixed] Returns a deserialized type-casted value. + def deserialize serialized_value + self.class.deserialize(serialized_value, options) + end + + # Takes the type casted value and serializes it + # @param [Mixed] A single value to serialize. + # @return [Mixed] Returns the serialized value. + def serialize type_casted_value + self.class.serialize(type_casted_value, options) + end + + # @param [String] serialized_value The string value as returned from AWS. + # @return [Mixed] Returns the type-casted deserialized value. + def self.deserialize serialized_value, options = {} + self.type_cast(serialized_value, options) + end + + # @return [Boolean] Returns true if this attribute type can be used + # with the +:set => true+ option. Certain attirbutes can not + # be represented with multiple values (like BooleanAttribute). + def self.allow_set? + raise 'allow_set? must be defined in subclasses' + end + + # @private + protected + def self.expect klass, value, &block + unless value.is_a?(klass) + raise ArgumentError, "expected a #{klass} value, got #{value.class}" + end + yield + end + + end + end +end diff --git a/lib/aws/record/attribute_macros.rb b/lib/aws/record/attribute_macros.rb new file mode 100644 index 00000000000..e69af904bba --- /dev/null +++ b/lib/aws/record/attribute_macros.rb @@ -0,0 +1,288 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/attributes/string' +require 'aws/record/attributes/integer' +require 'aws/record/attributes/sortable_integer' +require 'aws/record/attributes/float' +require 'aws/record/attributes/sortable_float' +require 'aws/record/attributes/boolean' +require 'aws/record/attributes/datetime' + +module AWS + module Record + module AttributeMacros + + # Adds a string attribute to this class. + # + # @example A standard string attribute + # + # class Recipe < AWS::Record::Base + # string_attr :name + # end + # + # recipe = Recipe.new(:name => "Buttermilk Pancakes") + # recipe.name #=> 'Buttermilk Pancakes' + # + # @example A string attribute with +:set+ set to true + # + # class Recipe < AWS::Record::Base + # string_attr :tags, :set => true + # end + # + # recipe = Recipe.new(:tags => %w(popular dessert)) + # recipe.tags #=> # + # + # @param [Symbol] name The name of the attribute. + # @param [Hash] options + # @option options [Boolean] :set (false) When true this attribute + # can have multiple values. + def string_attr name, options = {} + add_attribute(StringAttribute.new(name, options)) + end + + # Adds an integer attribute to this class. + # + # class Recipe < AWS::Record::Base + # integer_attr :servings + # end + # + # recipe = Recipe.new(:servings => '10') + # recipe.servings #=> 10 + # + # @param [Symbol] name The name of the attribute. + # @param [Hash] options + # @option options [Boolean] :set (false) When true this attribute + # can have multiple values. + def integer_attr name, options = {} + add_attribute(IntegerAttribute.new(name, options)) + end + + # Adds a sortable integer attribute to this class. + # + # class Person < AWS::Record::Base + # sortable_integer_attr :age, :range => 0..150 + # end + # + # person = Person.new(:age => 10) + # person.age #=> 10 + # + # === Validations + # + # It is recomended to apply a validates_numericality_of with + # minimum and maximum value constraints. If a value is assigned + # to a sortable integer that falls outside of the +:range: it will + # raise a runtime error when the record is saved. + # + # === Difference Between Sortable an Regular Integer Attributes + # + # Because SimpleDB does not support numeric types, all values must + # be converted to strings. This complicates sorting by numeric values. + # To accomplish sorting numeric attributes the values must be + # zero padded and have an offset applied to eliminate negative values. + # + # @param [Symbol] name The name of the attribute. + # @param [Hash] options + # @option options [Range] :range A numeric range the represents the + # minimum and maximum values this attribute should accept. + # @option options [Boolean] :set (false) When true this attribute + # can have multiple values. + def sortable_integer_attr name, options = {} + add_attribute(SortableIntegerAttribute.new(name, options)) + end + + # Adds a float attribute to this class. + # + # class Listing < AWS::Record::Base + # float_attr :score + # end + # + # listing = Listing.new(:score => '123.456') + # listing.score # => 123.456 + # + # @param [Symbol] name The name of the attribute. + # @param [Hash] options + # @option options [Boolean] :set (false) When true this attribute + # can have multiple values. + def float_attr name, options = {} + add_attribute(FloatAttribute.new(name, options)) + end + + # Adds sortable float attribute to this class. + # + # Persisted values are stored (and sorted) as strings. This makes it + # more difficult to sort numbers because they don't sort + # lexicographically unless they have been offset to be positive and + # then zero padded. + # + # === Postive Floats + # + # To store floats in a sort-friendly manor: + # + # sortable_float_attr :score, :range => (0..10) + # + # This will cause values like 5.5 to persist as a string like '05.5' so + # that they can be sorted lexicographically. + # + # === Negative Floats + # + # If you need to store negative sortable floats, increase your +:range+ + # to include a negative value. + # + # sortable_float_attr :position, :range => (-10..10) + # + # AWS::Record will add 10 to all values and zero pad them + # (e.g. -10.0 will be represented as '00.0' and 10 will be represented as + # '20.0'). This will allow the values to be compared lexicographically. + # + # @note If you change the +:range+ after some values have been persisted + # you must also manually migrate all of the old values to have the + # correct padding & offset or they will be interpreted differently. + # + # @param [Symbol] name The name of the attribute. + # @param [Hash] options + # @option options [Range] :range The range of numbers this attribute + # should represent. The min and max values of this range will determine + # how many digits of precision are required and how much of an offset + # is required to make the numbers sort lexicographically. + # @option options [Boolean] :set (false) When true this attribute + # can have multiple values. + def sortable_float_attr name, options = {} + add_attribute(SortableFloatAttribute.new(name, options)) + end + + # Adds a boolean attribute to this class. + # + # @example + # + # class Book < AWS::Record::Base + # boolean_attr :read + # end + # + # b = Book.new + # b.read? # => false + # b.read = true + # b.read? # => true + # + # listing = Listing.new(:score => '123.456' + # listing.score # => 123.456 + # + # @param [Symbol] name The name of the attribute. + def boolean_attr name, options = {} + + attr = add_attribute(BooleanAttribute.new(name, options)) + + # add the boolean question mark method + define_method("#{attr.name}?") do + !!__send__(attr.name) + end + + end + + # Adds a datetime attribute to this class. + # + # @example A standard datetime attribute + # + # class Recipe < AWS::Record::Base + # datetime_attr :invented + # end + # + # recipe = Recipe.new(:invented => Time.now) + # recipe.invented #=> + # + # If you add a datetime_attr for +:created_at+ and/or +:updated_at+ those + # will be automanaged. + # + # @param [Symbol] name The name of the attribute. + # @param [Hash] options + # @option options [Integer] :precision When set, the integer will be + # serialized with the correct number of digits to SimpleDB, left + # padded by zeros to allow sorting. + # @option options [Boolean] :set (false) When true this attribute + # can have multiple values. + def datetime_attr name, options = {} + add_attribute(DateTimeAttribute.new(name, options)) + end + + # A convenience method for adding the standard two datetime attributes + # +:created_at+ and +:updated_at+. + # + # @example + # + # class Recipe < AWS::Record::Base + # timestamps + # end + # + # recipe = Recipe.new + # recipe.save + # recipe.created_at #=> + # recipe.updated_at #=> + # + def timestamps + c = datetime_attr :created_at + u = datetime_attr :updated_at + [c, u] + end + + # @private + private + def add_attribute attribute + + attr_name = attribute.name + + attributes[attr_name] = attribute + + # setter + define_method("#{attr_name}=") do |value| + self[attr_name] = value + end + + # getter + define_method(attr_name) do + self[attr_name] + end + + # before type-cast getter + define_method("#{attr_name}_before_type_cast") do + @_data[attr_name] + end + + ## dirty tracking methods + + define_method("#{attr_name}_changed?") do + attribute_changed?(attr_name) + end + + define_method("#{attr_name}_change") do + attribute_change(attr_name) + end + + define_method("#{attr_name}_was") do + attribute_was(attr_name) + end + + define_method("#{attr_name}_will_change!") do + attribute_will_change!(attr_name) + end + + define_method("reset_#{attr_name}!") do + reset_attribute!(attr_name) + end + + attribute + + end + + end + end +end diff --git a/lib/aws/record/attributes/boolean.rb b/lib/aws/record/attributes/boolean.rb new file mode 100644 index 00000000000..8236acb435f --- /dev/null +++ b/lib/aws/record/attributes/boolean.rb @@ -0,0 +1,49 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/attribute' + +module AWS + module Record + + # @private + class BooleanAttribute < Attribute + + def self.type_cast raw_value, options = {} + case raw_value + when nil then nil + when '' then nil + when false, 'false', '0', 0 then false + else true + end + end + + def self.serialize boolean, options = {} + case boolean + when false then '0' + when true then '1' + else + msg = "expected a boolean value, got #{boolean.class}" + raise ArgumentError, msg + end + end + + # @private + def self.allow_set? + false + end + + end + + end +end diff --git a/lib/aws/record/attributes/datetime.rb b/lib/aws/record/attributes/datetime.rb new file mode 100644 index 00000000000..84750b89e9d --- /dev/null +++ b/lib/aws/record/attributes/datetime.rb @@ -0,0 +1,86 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/attribute' + +module AWS + module Record + + # @private + class DateTimeAttribute < Attribute + + # Returns value cast to a DateTime object. Empty strings are cast to + # nil. Values are cast first to strings and then passed to + # DateTime.parse. Integers are treated as timestamps. + # + # datetime_attribute.type_cast('2000-01-02') + # #=> # + # + # datetime_attribute.type_cast(1306170146) + # # + # + # datetime_attribute.type_cast('') + # #=> nil + # + # datetime_attribute.type_cast(nil) + # #=> nil + # + # @param [Mixed] raw_value The value to cast to a DateTime object. + # @param [Hash] options + # @return [DateTime,nil] + def self.type_cast raw_value, options = {} + case raw_value + when nil then nil + when '' then nil + when DateTime then raw_value + when Integer then + begin + DateTime.parse(Time.at(raw_value).to_s) # timestamp + rescue + nil + end + else + begin + DateTime.parse(raw_value.to_s) # Time, Date or String + rescue + nil + end + end + end + + # Returns a DateTime object encoded as a string (suitable for sorting). + # + # attribute.serialize(DateTime.parse('2001-01-01')) + # #=> '2001-01-01T00:00:00:Z) + # + # @param [DateTime] datetime The datetime object to serialize. + # @param [Hash] options + # @return [String] Returns the datetime object serialized to a string + # in ISO8601 format (e.g. '2011-01-02T10:11:12Z') + def self.serialize datetime, options = {} + unless datetime.is_a?(DateTime) + msg = "expected a DateTime value, got #{datetime.class}" + raise ArgumentError, msg + end + datetime.strftime('%Y-%m-%dT%H:%M:%S%Z') + end + + # @private + def self.allow_set? + true + end + + end + + end +end diff --git a/lib/aws/record/attributes/float.rb b/lib/aws/record/attributes/float.rb new file mode 100644 index 00000000000..0006cc6cecb --- /dev/null +++ b/lib/aws/record/attributes/float.rb @@ -0,0 +1,48 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/attribute' + +module AWS + module Record + + # @private + class FloatAttribute < Attribute + + def self.type_cast raw_value, options = {} + case raw_value + when nil then nil + when '' then nil + when Float then raw_value + else + raw_value.respond_to?(:to_f) ? + raw_value.to_f : + raw_value.to_s.to_f + end + end + + def self.serialize float, options = {} + expect(Float, float) do + float.to_s + end + end + + # @private + def self.allow_set? + true + end + + end + + end +end diff --git a/lib/aws/record/attributes/integer.rb b/lib/aws/record/attributes/integer.rb new file mode 100644 index 00000000000..c554f72f9cb --- /dev/null +++ b/lib/aws/record/attributes/integer.rb @@ -0,0 +1,68 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/attribute' + +module AWS + module Record + + # @private + class IntegerAttribute < Attribute + + # Returns value cast to an integer. Empty strings are cast to + # nil by default. Type casting is done by calling #to_i on the value. + # + # int_attribute.type_cast('123') + # #=> 123 + # + # int_attribute.type_cast('') + # #=> nil + # + # @param [Mixed] value The value to type cast to an integer. + # @return [Integer,nil] Returns the type casted integer or nil + def self.type_cast raw_value, options = {} + case raw_value + when nil then nil + when '' then nil + when Integer then raw_value + else + raw_value.respond_to?(:to_i) ? + raw_value.to_i : + raw_value.to_s.to_i + end + end + + # Returns a serialized representation of the integer value suitable for + # storing in SimpleDB. + # + # attribute.serialize(123) + # #=> '123' + # + # @param [Integer] integer The number to serialize. + # @param [Hash] options + # @return [String] A serialized representation of the integer. + def self.serialize integer, options = {} + expect(Integer, integer) do + integer.to_s + end + end + + # @private + def self.allow_set? + true + end + + end + + end +end diff --git a/lib/aws/record/attributes/sortable_float.rb b/lib/aws/record/attributes/sortable_float.rb new file mode 100644 index 00000000000..6e262ea3ce6 --- /dev/null +++ b/lib/aws/record/attributes/sortable_float.rb @@ -0,0 +1,60 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/attribute' + +module AWS + module Record + + # @private + class SortableFloatAttribute < FloatAttribute + + def initialize name, options = {} + + range = options[:range] + raise ArgumentError, "missing required option :range" unless range + raise ArgumentError, ":range should be an integer range" unless + range.is_a?(Range) and range.first.is_a?(Integer) + + super(name, options) + + end + + def self.serialize float, options = {} + expect(Float, float) do + + left, right = float.to_s.split('.') + + left = SortableIntegerAttribute.serialize(left.to_i, options) + + SortableIntegerAttribute.check_range(float, options) + + "#{left}.#{right}" + + end + end + + def self.deserialize string_value, options = {} + + left, right = float.to_s.split('.') + + left = SortableIntegerAttribute.deserialize(left, options) + + "#{left}.#{right}".to_f + + end + + end + + end +end diff --git a/lib/aws/record/attributes/sortable_integer.rb b/lib/aws/record/attributes/sortable_integer.rb new file mode 100644 index 00000000000..f5d597f0dd4 --- /dev/null +++ b/lib/aws/record/attributes/sortable_integer.rb @@ -0,0 +1,95 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/attributes/integer' + +module AWS + module Record + + # @private + class SortableIntegerAttribute < IntegerAttribute + + def initialize name, options = {} + + range = options[:range] + raise ArgumentError, "missing required option :range" unless range + raise ArgumentError, ":range should be a integer range" unless + range.is_a?(Range) and range.first.is_a?(Integer) + + super(name, options) + + end + + # Returns a serialized representation of the integer value suitable for + # storing in SimpleDB. + # + # attribute.serialize(123) + # #=> '123' + # + # # padded to the correct number of digits + # attribute.serialize('123', :range => (0..10_000) + # #=> '00123' + # + # # offset applied to make all values positive + # attribute.serialize('-55', :range => (-100..10_000) + # #=> '00045' + # + # @param [Integer] integer The number to serialize. + # @param [Hash] options + # @option options [required,Range] :range A range that represents the + # minimum and maximum values this integer can be. + # The returned value will have an offset applied (if min is + # less than 0) and will be zero padded. + # @return [String] A serialized representation of the integer. + def self.serialize integer, options = {} + expect(Integer, integer) do + check_range(integer, options) + offset_and_precision(options) do |offset,precision| + "%0#{precision}d" % (integer.to_i + offset) + end + end + end + + def self.deserialize string_value, options = {} + offset_and_precision(options) do |offset,precision| + string_value.to_i - offset + end + end + + # @private + protected + def self.offset_and_precision options, &block + + min = options[:range].first + max = options[:range].last + + offset = min < 0 ? min * -1 : 0 + precision = (max + offset).to_s.length + + yield(offset, precision) + + end + + # @private + def self.check_range number, options + unless options[:range].include?(number) + msg = "unable to serialize `#{number}`, falls outside " + + "the range #{options[:range]}" + raise msg + end + end + + end + + end +end diff --git a/lib/aws/record/attributes/string.rb b/lib/aws/record/attributes/string.rb new file mode 100644 index 00000000000..2231639902c --- /dev/null +++ b/lib/aws/record/attributes/string.rb @@ -0,0 +1,69 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/attribute' + +module AWS + module Record + + # @private + class StringAttribute < Attribute + + # Returns the value cast to a string. Empty strings are returned as + # nil by default. Type casting is done by calling #to_s on the value. + # + # string_attr.type_cast(123) + # # => '123' + # + # string_attr.type_cast('') + # # => nil + # + # string_attr.type_cast('', :preserve_empty_strings => true) + # # => '' + # + # @param [Mixed] value + # @param [Hash] options + # @option options [Boolean] :preserve_empty_strings (false) When true, + # empty strings are preserved and not cast to nil. + # @return [String,nil] The type casted value. + def self.type_cast raw_value, options = {} + case raw_value + when nil then nil + when '' then options[:preserve_empty_strings] ? '' : nil + when String then raw_value + else raw_value.to_s + end + end + + # Returns a serialized representation of the string value suitable for + # storing in SimpleDB. + # @param [String] string + # @param [Hash] options + # @return [String] The serialized string. + def self.serialize string, options = {} + unless string.is_a?(String) + msg = "expected a String value, got #{string.class}" + raise ArgumentError, msg + end + string + end + + # @private + def self.allow_set? + true + end + + end + + end +end diff --git a/lib/aws/record/base.rb b/lib/aws/record/base.rb new file mode 100644 index 00000000000..b4a5b759f7a --- /dev/null +++ b/lib/aws/record/base.rb @@ -0,0 +1,728 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# todo move these to included modules (like validations and naming) + +require 'set' +require 'uuidtools' +require 'aws/indifferent_hash' + +require 'aws/record/naming' +require 'aws/record/attribute_macros' +require 'aws/record/finder_methods' +require 'aws/record/validations' +require 'aws/record/dirty_tracking' +require 'aws/record/conversion' +require 'aws/record/errors' +require 'aws/record/exceptions' + +module AWS + module Record + + # An ActiveRecord-like interface built ontop of AWS. + # + # class Book < AWS::Record::Base + # + # string_attr :title + # string_attr :author + # integer :number_of_pages + # + # timestamps # adds a :created_at and :updated_at pair of timestamps + # + # end + # + # b = Book.new(:title => 'My Book', :author => 'Me', :pages => 1) + # b.save + # + # = Attribute Macros + # + # When extending AWS::Record::Base you should first consider what + # attributes your class should have. Unlike ActiveRecord, AWS::Record + # models are not backed by a database table/schema. You must choose what + # attributes (and what types) you need. + # + # * +string_attr+ + # * +boolean_attr+ + # * +integer_attr+ + # * +float_attr+ + # * +datetime_attr+ + # + # For more information about the various attribute macros available, + # and what options they accept, see {AttributeMacros}. + # + # === Usage + # + # Normally you just call these methods inside your model class definition: + # + # class Book < AWS::Record::Base + # string_attr :title + # boolean_attr :has_been_read + # integer_attr :number_of_pages + # float_attr :weight_in_pounds + # datetime_attr :published_at + # end + # + # For each attribute macro a pair of setter/getter methods are added # + # to your class (and a few other useful methods). + # + # b = Book.new + # b.title = "My Book" + # b.has_been_read = true + # b.number_of_pages = 1000 + # b.weight_in_pounds = 1.1 + # b.published_at = Time.now + # b.save + # + # b.id #=> "0aa894ca-8223-4d34-831e-e5134b2bb71c" + # b.attributes + # #=> { 'title' => 'My Book', 'has_been_read' => true, ... } + # + # === Default Values + # + # All attribute macros accept the +:default_value+ option. This sets + # a value that is populated onto all new instnaces of the class. + # + # class Book < AWS::Record::Base + # string_attr :author, :deafult_value => 'Me' + # end + # + # Book.new.author #=> 'Me' + # + # === Multi-Valued (Set) Attributes + # + # AWS::Record permits storing multiple values with a single attribute. + # + # class Book < AWS::Record::Base + # string_attr :tags, :set => true + # end + # + # b = Book.new + # b.tags #=> # + # + # b.tags = ['fiction', 'fantasy'] + # b.tags #=> # + # + # These multi-valued attributes are treated as sets, not arrays. This + # means: + # + # * values are unordered + # * duplicate values are automatically omitted + # + # Please consider these limitations when you choose to use the +:set+ + # option with the attribute macros. + # + # = Validations + # + # It's important to validate models before there are persisted to keep + # your data clean. AWS::Record supports most of the ActiveRecord style + # validators. + # + # class Book < AWS::Record::Base + # string_attr :title + # validates_presence_of :title + # end + # + # b = Book.new + # b.valid? #=> false + # b.errors.full_messages #=> ['Title may not be blank'] + # + # Validations are checked before saving a record. If any of the validators + # adds an error, the the save will fail. + # + # For more information about the available validation methods see + # {Validations}. + # + # = Finder Methods + # + # You can find records by their ID. Each record gets a UUID when it + # is saved for the first time. You can use this ID to fetch the record + # at a latter time: + # + # b = Book["0aa894ca-8223-4d34-831e-e5134b2bb71c"] + # + # b = Book.find("0aa894ca-8223-4d34-831e-e5134b2bb71c") + # + # If you try to find a record by ID that has no data an error will + # be raised. + # + # === All + # + # You can enumerate all of your records using +all+. + # + # Book.all.each do |book| + # puts book.id + # end + # + # Book.find(:all) do |book| + # puts book.id + # end + # + # Be careful when enumerating all. Depending on the number of records + # and number of attributes each record has, this can take a while, + # causing quite a few requests. + # + # === First + # + # If you only want a single record, you should use +first+. + # + # b = Book.first + # + # === Modifiers + # + # Frequently you do not want ALL records or the very first record. You + # can pass options to +find+, +all+ and +first+. + # + # my_books = Book.find(:all, :where => 'owner = "Me"') + # + # book = Book.first(:where => { :has_been_read => false }) + # + # You can pass as find options: + # + # * +:where+ - Conditions that must be met to be returned + # * +:order+ - The order to sort matched records by + # * +:limit+ - The maximum number of records to return + # + # = Scopes + # + # More useful than writing query fragments all over the place is to + # name your most common conditions for reuse. + # + # class Book < AWS::Record::Base + # + # scope :mine, where(:owner => 'Me') + # + # scope :unread, where(:has_been_read => false) + # + # scope :by_popularity, order(:score, :desc) + # + # scope :top_10, by_popularity.limit(10) + # + # end + # + # # The following expression returns 10 books that belong + # # to me, that are unread sorted by popularity. + # next_good_reads = Book.mine.unread.top_10 + # + # There are 3 standard scope methods: + # + # * +where+ + # * +order+ + # * +limit+ + # + # === Conditions (where) + # + # Where accepts aruments in a number of forms: + # + # 1. As an sql-like fragment. If you need to escape values this form is + # not suggested. + # + # Book.where('title = "My Book"') + # + # 2. An sql-like fragment, with placeholders. This escapes quoted + # arguments properly to avoid injection. + # + # Book.where('title = ?', 'My Book') + # + # 3. A hash of key-value pairs. This is the simplest form, but also the + # least flexible. You can not use this form if you need more complex + # expressions that use or. + # + # Book.where(:title => 'My Book') + # + # === Order + # + # This orders the records as returned by AWS. Default ordering is ascending. + # Pass the value :desc as a second argument to sort in reverse ordering. + # + # Book.order(:title) # alphabetical ordering + # Book.order(:title, :desc) # reverse alphabetical ordering + # + # You may only order by a single attribute. If you call order twice in the + # chain, the last call gets presedence: + # + # Book.order(:title).order(:price) + # + # In this example the books will be ordered by :price and the order(:title) + # is lost. + # + # === Limit + # + # Just call +limit+ with an integer argument. This sets the maximum + # number of records to retrieve: + # + # Book.limit(2) + # + # === Delayed Execution + # + # It should be noted that all finds are lazy (except +first+). This + # means the value returned is not an array of records, rather a handle + # to a {Scope} object that will return records when you enumerate over them. + # + # This allows you to build an expression without making unecessary requests. + # In the following example no request is made until the call to + # each_with_index. + # + # all_books = Books.all + # ten_books = all_books.limit(10) + # + # ten_books.each_with_index do |book,n| + # puts "#{n + 1} : #{book.title}" + # end + # + class Base + + # for rails 3+ active model compatability + extend Naming + include Naming + + extend Validations + extend AttributeMacros + extend FinderMethods + include Conversion + include DirtyTracking + + # Constructs a new record for this class/domain. + # + # @param [Hash] attributes A set of attribute values to seed this record + # with. The attributes are bulk assigned. + # @return [Base] Returns a new record that has not been persisted yet. + def initialize attributes = {} + @_data = {} + assign_default_values + bulk_assign(attributes) + end + + # The id for each record is auto-generated. The default strategy + # generates uuid strings. + # @return [String] Returns the id string (uuid) for this record. Retuns + # nil if this is a new record that has not been persisted yet. + def id + @_id + end + + # @return [Hash] A hash with attribute names as hash keys (strings) and + # attribute values (of mixed types) as hash values. + def attributes + attributes = IndifferentHash.new + attributes['id'] = id if persisted? + self.class.attributes.keys.inject(attributes) do |hash,attr_name| + hash[attr_name] = __send__(attr_name) + hash + end + end + + # Persistence indicates if the record has been saved previously or not. + # + # @example + # @recipe = Recipe.new(:name => 'Buttermilk Pancackes') + # @recipe.persisted? #=> false + # @recipe.save! + # @recipe.persisted? #=> true + # + # @return [Boolean] Returns true if this record has been persisted. + def persisted? + !!@_persisted + end + + # @return [Boolean] Returns true if this record has not been persisted + # to SimpleDB. + def new_record? + !persisted? + end + + # @return [Boolean] Returns true if this record has no validation errors. + def valid? + validate + errors.empty? + end + + # Creates new records, updates existing records. + # @return [Boolean] Returns true if the record saved without errors, + # false otherwise. + def save + if valid? + persisted? ? update : create + clear_changes! + true + else + false + end + end + + # Creates new records, updates exsting records. If there is a validation + # error then an exception is raised. + # @raise [InvalidRecordError] Raised when the record has validation + # errors and can not be saved. + # @return [true] Returns true after a successful save. + def save! + raise InvalidRecordError.new(self) unless save + true + end + + # Bulk assigns the attributes and then saves the record. + # @param [Hash] attribute_hash A hash of attribute names (keys) and + # attribute values to assign to this record. + # @return (see #save) + def update_attributes attribute_hash + bulk_assign(attribute_hash) + save + end + + # Bulk assigns the attributes and then saves the record. Raises + # an exception (AWS::Record::InvalidRecordError) if the record is not + # valid. + # @param (see #update_attributes) + # @return [true] + def update_attributes! attribute_hash + if update_attributes(attribute_hash) + true + else + raise InvalidRecordError.new(self) + end + end + + # Deletes the record. + # @return (see #delete_item) + def delete + if persisted? + if deleted? + raise 'unable to delete, this object has already been deleted' + else + delete_item + end + else + raise 'unable to delete, this object has not been saved yet' + end + end + + # @return [Boolean] Returns true if this instance object has been deleted. + def deleted? + persisted? ? !!@_deleted : false + end + + # If you define a custom setter, you use #[]= to set the value + # on the record. + # + # class Book < AWS::Record::Base + # + # string_attr :name + # + # # replace the default #author= method + # def author= name + # self['author'] = name.blank? ? 'Anonymous' : name + # end + # + # end + # + # @param [String,Symbol] The attribute name to set a value for + # @param attribute_value The value to assign. + protected + def []= attribute_name, new_value + self.class.attribute_for(attribute_name) do |attribute| + + if_tracking_changes do + original_value = type_cast(attribute, attribute_was(attribute.name)) + incoming_value = type_cast(attribute, new_value) + if original_value == incoming_value + clear_change!(attribute.name) + else + attribute_will_change!(attribute.name) + end + end + + @_data[attribute.name] = new_value + + end + end + + # Returns the typecasted value for the named attribute. + # + # book = Book.new(:title => 'My Book') + # book['title'] #=> 'My Book' + # book.title #=> 'My Book' + # + # === Intended Use + # + # This method's primary use is for getting/setting the value for + # an attribute inside a custom method: + # + # class Book < AWS::Record::Base + # + # string_attr :title + # + # def title + # self['title'] ? self['title'].upcase : nil + # end + # + # end + # + # book = Book.new(:title => 'My Book') + # book.title #=> 'MY BOOK' + # + # @param [String,Symbol] attribute_name The name of the attribute to fetch + # a value for. + # @return The current type-casted value for the named attribute. + protected + def [] attribute_name + self.class.attribute_for(attribute_name) do |attribute| + type_cast(attribute, @_data[attribute.name]) + end + end + + # @return [SimpleDB::Item] Returns a reference to the item as stored in + # simple db. + # @private + private + def sdb_item + self.class.sdb_domain.items[id] + end + + # @private + private + def assign_default_values + # populate default attribute values + ignore_changes do + self.class.attributes.values.each do |attribute| + begin + # copy default values down so methods like #gsub! don't + # modify the default values for other objects + @_data[attribute.name] = attribute.default_value.clone + rescue TypeError + @_data[attribute.name] = attribute.default_value + end + end + end + end + + # @return [true] + # @private + private + def delete_item + options = {} + add_optimistic_lock_expectation(options) + sdb_item.delete(options) + @_deleted = true + end + + # @private + private + def bulk_assign hash + hash.each_pair do |attribute_name, attribute_value| + __send__("#{attribute_name}=", attribute_value) + end + end + + # @private + # @todo need to do something about partial hyrdation of attributes + private + def hydrate id, data + @_id = id + + # New objects are populated with default values, but we don't + # want these values to hang around when hydrating persisted values + # (those values may have been blanked out before save). + self.class.attributes.values.each do |attribute| + @_data[attribute.name] = nil + end + + ignore_changes do + bulk_assign(deserialize_item_data(data)) + end + + @_persisted = true + + end + + # This function accepts a hash of item data (as returned from + # AttributeCollection#to_h or ItemData#attributes) and returns only + # the key/value pairs that are configured attribues for this class. + # @private + private + def deserialize_item_data item_data + + marked_for_deletion = item_data['_delete_'] || [] + + data = {} + item_data.each_pair do |attr_name,values| + + attribute = self.class.attributes[attr_name] + + next unless attribute + next if marked_for_deletion.include?(attr_name) + + if attribute.set? + data[attr_name] = values.map{|v| attribute.deserialize(v) } + else + data[attr_name] = attribute.deserialize(values.first) + end + + end + data + end + + # @private + private + def create + + populate_id + touch_timestamps('created_at', 'updated_at') + increment_optimistic_lock_value + + to_add = serialize_attributes + + add_optimistic_lock_expectation(to_add) + sdb_item.attributes.add(to_add) + + @_persisted = true + + end + + # @private + private + def update + + return unless changed? + + touch_timestamps('updated_at') + increment_optimistic_lock_value + + to_update = {} + to_delete = [] + + # serialized_attributes will raise error if the entire record is blank + attribute_values = serialize_attributes + + changed.each do |attr_name| + if values = attribute_values[attr_name] + to_update[attr_name] = values + else + to_delete << attr_name + end + end + + add_optimistic_lock_expectation(to_update) + + if to_delete.empty? + sdb_item.attributes.replace(to_update) + else + sdb_item.attributes.replace(to_update.merge('_delete_' => to_delete)) + sdb_item.attributes.delete(to_delete + ['_delete_']) + end + + end + + # @private + private + def serialize_attributes + + hash = {} + self.class.attributes.each_pair do |attribute_name,attribute| + values = serialize(attribute, @_data[attribute_name]) + unless values.empty? + hash[attribute_name] = values + end + end + + # simple db does not support persisting items without attribute values + raise EmptyRecordError.new(self) if hash.empty? + + hash + + end + + # @private + private + def increment_optimistic_lock_value + if_locks_optimistically do |lock_attr_name| + if value = self[lock_attr_name] + self[lock_attr_name] += 1 + else + self[lock_attr_name] = 1 + end + end + end + + # @private + private + def add_optimistic_lock_expectation options + if_locks_optimistically do |lock_attr_name| + was = attribute_was(lock_attr_name) + if was + options[:if] = { lock_attr_name => was.to_s } + else + options[:unless] = lock_attr_name + end + end + end + + private + def if_locks_optimistically &block + if opt_lock_attr = self.class.optimistic_locking_attr + yield(opt_lock_attr.name) + end + end + + # @private + private + def populate_id + @_id = UUIDTools::UUID.random_create.to_s + end + + # @private + private + def touch_timestamps *attributes + time = Time.now + attributes.each do |attr_name| + if self.class.attributes[attr_name] and !attribute_changed?(attr_name) + __send__("#{attr_name}=", time) + end + end + end + + # @private + private + def type_cast attribute, raw + if attribute.set? + values = Record.as_array(raw).inject([]) do |values,value| + values << attribute.type_cast(value) + values + end + Set.new(values.compact) + else + attribute.type_cast(raw) + end + end + + # @private + private + def serialize attribute, raw + type_casted = type_cast(attribute, raw) + Record.as_array(type_casted).inject([]) do |values, value| + values << attribute.serialize(value) + values + end + end + + # @private + private + def self.attribute_for attribute_name, &block + unless attributes[attribute_name.to_s] + raise UndefinedAttributeError.new(attribute_name.to_s) + end + yield(attributes[attribute_name.to_s]) + end + + end + + end +end diff --git a/lib/aws/record/conversion.rb b/lib/aws/record/conversion.rb new file mode 100644 index 00000000000..4d7ac81b2ec --- /dev/null +++ b/lib/aws/record/conversion.rb @@ -0,0 +1,38 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + module Record + + # @private + module Conversion + + # @private + def to_model + self + end + + # @private + def to_key + persisted? ? [id] : nil + end + + # @private + def to_param + persisted? ? to_key.join('-') : nil + end + + end + + end +end diff --git a/lib/aws/record/dirty_tracking.rb b/lib/aws/record/dirty_tracking.rb new file mode 100644 index 00000000000..2cf6261c6cf --- /dev/null +++ b/lib/aws/record/dirty_tracking.rb @@ -0,0 +1,286 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + module Record + + + # Provides a way to track changes in your records. + # + # my_book = Book['bookid'] + # + # my_book.changed? #=> false + # my_book.title #=> "My Book" + # my_book.title = "My Awesome Book" + # my_book.changed? #=> true + # + # You can inspect further and get a list of changed attributes + # + # my_book.changed #=> ['title'] + # + # Or you can get a more detailed description of the changes. {#changes} + # returns a hash of changed attributes (keys) with their old and new + # values. + # + # my_book.changes + # #=> { 'title' => ['My Book', 'My Awesome Book'] + # + # For every configured attribute you also get a handful of methods + # for inspecting changes on that attribute. Given the following + # attribute: + # + # string_attr :title + # + # You can now call any of the following methods: + # + # * title_changed? + # * title_change + # * title_was + # * reset_title! + # * title_will_change! + # + # Given the title change from above: + # + # my_book.title_changed? #=> true + # my_book.title_change #=> ['My Book', 'My Awesome Book'] + # my_book.title_was #=> ['My Book'] + # + # my_book.reset_title! + # my_book.title #=> 'My Book' + # + # == In-Place Editing + # + # Dirty tracking works by comparing incoming attribute values upon + # assignment against the value that was there previously. If you + # use functions against the value that modify it (like gsub!) + # you must notify your record about the coming change. + # + # my_book.title #=> 'My Book' + # my_book.title_will_change! + # my_book.title.gsub!(/My/, 'Your') + # my_book.title_change #=> ['My Book', 'Your Book'] + # + # == Partial Updates + # + # Dirty tracking makes it possible to only persist those attributes + # that have changed since they were loaded. This speeds up requests + # against AWS when saving data. + # + module DirtyTracking + + # Returns true if this model has unsaved changes. + # + # b = Book.new(:title => 'My Book') + # b.changed? + # #=> true + # + # New objects and objects freshly loaded should not have any changes: + # + # b = Book.new + # b.changed? #=> false + # + # b = Book.first + # b.changed? #=> false + # + # @return [Boolean] Returns true if any of the attributes have + # unsaved changes. + def changed? + !orig_values.empty? + end + + # Returns an array of attribute names that have changes. + # + # book.changed #=> [] + # person.title = 'New Title' + # book.changed #=> ['title'] + # + # @return [Array] Returns an array of attribute names that have + # unsaved changes. + def changed + orig_values.keys + end + + # Returns the changed attributes in a hash. Keys are attribute names, + # values are two value arrays. The first value is the previous + # attribute value, the second is the current attribute value. + # + # book.title = 'New Title' + # book.changes + # #=> { 'title' => ['Old Title', 'New Title'] } + # + # @return [Hash] Returns a hash of attribute changes. + def changes + changed.inject({}) do |changes, attr_name| + changes[attr_name] = attribute_change(attr_name) + changes + end + end + + # Returns true if the named attribute has unsaved changes. + # + # This is an attribute method. The following two expressions + # are equivilent: + # + # book.title_changed? + # book.attribute_changed?(:title) + # + # @param [String] attribute_name Name of the attribute to check + # for changes. + # + # @return [Boolean] Returns true if the named attribute + # has unsaved changes. + # @private + private + def attribute_changed? attribute_name + orig_values.keys.include?(attribute_name) + end + + # Returns an array of the old value and the new value for + # attributes that have unsaved changes, returns nil otherwise. + # + # This is an attribute method. The following two expressions + # are equivilent: + # + # book.title_change + # book.attribute_change(:title) + # + # @example Asking for changes on an unchanged attribute + # + # book = Book.new + # book.title_change #=> nil + # + # @example Getting changed attributes on a new object + # + # book = Book.new(:title => 'My Book') + # book.title_change #=> [nil, 'My Book'] + # + # @example Getting changed attributes on a loaded object + # + # book = Book.first + # book.title = 'New Title' + # book.title_change #=> ['Old Title', 'New Title'] + # + # @param [String] attribute_name Name of the attribute to fetch + # a change for. + # @return [Boolean] Returns true if the named attribute + # has unsaved changes. + # @private + private + def attribute_change attribute_name + self.class.attribute_for(attribute_name) do |attribute| + if orig_values.has_key?(attribute.name) + [orig_values[attribute.name], __send__(attribute.name)] + else + nil + end + end + end + + # Returns the previous value for changed attributes, or the current + # value for unchanged attributes. + # + # This is an attribute method. The following two expressions + # are equivilent: + # + # book.title_was + # book.attribute_was(:title) + # + # @example Returns the previous value for changed attributes: + # + # book = Book.where(:title => 'My Book').first + # book.title = 'New Title' + # book.title_was #=> 'My Book' + # + # @example Returns the current value for unchanged attributes: + # + # book = Book.where(:title => 'My Book').first + # book.title_was #=> 'My Book' + # + # @return Returns the previous value for changed attributes + # or the current value for unchanged attributes. + # @private + private + def attribute_was attribute_name + self.class.attribute_for(attribute_name) do |attribute| + name = attribute.name + orig_values.has_key?(name) ? orig_values[name] : __send__(name) + end + end + + # Reverts any changes to the attribute, restoring its original value. + # @param [String] attribute_name Name of the attribute to reset. + # @return [nil] + # @private + private + def reset_attribute! attribute_name + __send__("#{attribute_name}=", attribute_was(attribute_name)) + nil + end + + # Indicate to the record that you are about to edit an attribute + # in place. + # @param [String] attribute_name Name of the attribute that will + # be changed. + # @return [nil] + # @private + private + def attribute_will_change! attribute_name + self.class.attribute_for(attribute_name) do |attribute| + name = attribute.name + unless orig_values.has_key?(name) + was = __send__(name) + begin + # booleans, nil, etc all #respond_to?(:clone), but they raise + # a TypeError when you attempt to dup them. + orig_values[name] = was.clone + rescue TypeError + orig_values[name] = was + end + end + end + nil + end + + private + def orig_values + @_orig_values ||= {} + end + + private + def clear_change! attribute_name + orig_values.delete(attribute_name) + end + + private + def ignore_changes &block + begin + @_ignore_changes = true + yield + ensure + @_ignore_changes = false + end + end + + private + def if_tracking_changes &block + yield unless @_ignore_changes + end + + private + def clear_changes! + orig_values.clear + end + + end + end +end diff --git a/lib/aws/record/errors.rb b/lib/aws/record/errors.rb new file mode 100644 index 00000000000..acf756199f1 --- /dev/null +++ b/lib/aws/record/errors.rb @@ -0,0 +1,153 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/indifferent_hash' + +module AWS + module Record + + class Base + + def errors + @errors ||= Errors.new + end + + end + + class Errors < IndifferentHash + + include Enumerable + + # Returns the errors for the atttibute in an array. + # + # errors.add(:name, 'may not be blank') + # errors.add(:name, 'must be less than 30 characters') + # errors[:name] + # #=> ['may not be blank', 'must be less than 30 characters'] + # + # @param [String,Symbol] attribute_name The name of the attribute to retnr + # errors for. You can pass the string or symbol version. + # @return [Array] Returns the error messages for the given + # +attribute_name+. If there are no errors on the attribute then + # an empty array is returned. + def [] attribute_name + super(attribute_name) || [] + end + alias_method :on, :[] + + # Adds an error message to the named attribute. + # + # errors.add(:name, 'may not be blank') + # errors.on(:name) + # #=> ['may not be blank'] + # + # If you want to add a general error message, then pass +:base+ + # for +attribute_name+, or call {#add_to_base}. + # @param [String,Symbol] attribute_name The name of the attribute + # that you are adding an error to. + # @param [String] message ('is invalid') The error message (should + # not contain the attribute name). + # @return [String] Returns the message. + def []= attribute_name, message = 'is invalid' + if has_key?(attribute_name) + self[attribute_name] << message + else + super(attribute_name, [message]) + end + self[attribute_name] + end + alias_method :add, :[]= + + # Adds a general error message (not associated with any particular + # attribute). + # @param [String] message ('is invalid') The error message (should + # not contain the attribute name). + # @return [String] Returns the message. + def add_to_base message + add(:base, message) + end + + # @return [Integer] Returns the number of error messages. + def count + values.flatten.length + end + alias_method :size, :count + + # Yields once for each error message added. + # + # An attribute_name may yield more than once if there are more than + # one errors associated with that attirbute. + # + # @yield [attribute_name, error_message] + # @yieldparam [String] attribute_name The name of the attribute + # @yieldparam [String] error_message The error message associated the + # the named attribute. + def each &block + super do |attribute_name, error_messages| + error_messages.each do |error_message| + yield(attribute_name, error_message) + end + end + end + + # Returns the errors prefixed by a humanized version of the attribute + # name. + # + # errors.add(:name, 'may not be blank') + # errors.full_messages + # #=> ['Name may not be blank'] + # + # @return [Array of Strings] Returns an array of error messages. + def full_messages + messages = [] + each do |attr_name, error_message| + messages << case attr_name + when 'base' then error_message.dup + else "#{attr_name.capitalize.gsub(/_/, ' ')} #{error_message}" + end + end + messages + end + alias_method :to_a, :full_messages + + # Returns a hash of of errors messages. Keys are attribute names + # and values are arrays of error messages. + # + # errors.add(:name, 'may not be blank') + # errors.to_hash + # #=> { 'name' => ['may not be blank'] } + # + # Please note that the hash values are always arrays, even if there + # is only one error message for the attribute. + def to_hash + hash = {} + each do |attr_name, message| + hash[attr_name] ||= [] + hash[attr_name] << message.dup + end + hash + end + + # Removes all error messages. + # @return [nil] + def clear! + keys.each do |key| + delete(key) + end + nil + end + + end + + end +end diff --git a/lib/aws/record/exceptions.rb b/lib/aws/record/exceptions.rb new file mode 100644 index 00000000000..6130d57826d --- /dev/null +++ b/lib/aws/record/exceptions.rb @@ -0,0 +1,48 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + module Record + + # Raised when trying to access an attribute that does not exist. + # @private + class UndefinedAttributeError < StandardError + def initalize attribute_name + super("undefined attribute `#{attribute_name}`") + end + end + + # Raised when calling #save! or #update_attributes! on a record that + # has validation errors. + # @private + class InvalidRecordError < StandardError + def initialize record + @record = record + super(record.errors.full_messages.join(', ')) + end + attr_reader :record + end + + # Raised when trying to persist a record that has no attribute values + # to persist. + # @private + class EmptyRecordError < StandardError + def initialize record + @record = record + super('unable persist empty records') + end + attr_reader :record + end + + end +end diff --git a/lib/aws/record/finder_methods.rb b/lib/aws/record/finder_methods.rb new file mode 100644 index 00000000000..f57dc8d8c1c --- /dev/null +++ b/lib/aws/record/finder_methods.rb @@ -0,0 +1,262 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/simple_db' +require 'aws/record/scope' + +module AWS + module Record + module FinderMethods + + # @param [String] id The id of the record to load. + # @raise [RecordNotFound] Raises a record not found exception if there + # was no data found for the given id. + # @return [Record::Base] Returns the record, as a sub-class of + # Record::Base + def [] id + + data = sdb_domain.items[id].data.attributes + + raise RecordNotFound, "no data found for id: #{id}" if data.empty? + + obj = self.new + obj.send(:hydrate, id, data) + obj + + end + + # Finds records in SimpleDB and returns them as objects of the + # current class. + # + # Finding +:all+ returns an enumerable scope object + # + # People.find(:all, :order => [:age, :desc], :limit => 10).each do |person| + # puts person.name + # end + # + # Finding +:first+ returns a single record (or nil) + # + # boss = People.find(:first, :where => { :boss => true }) + # + # Find accepts a hash of find modifiers (+:where+, +:order+ and + # +:limit+). You can also choose to omit these modifiers and + # chain them on the scope object returned. In the following + # example only one request is made to SimpleDB (when #each is + # called) + # + # people = People.find(:all) + # + # johns = people.where(:name => 'John Doe') + # + # johns.order(:age, :desc).limit(10).each do |suspects| + # # ... + # end + # + # See also {#where}, {#order} and {#limit} for more + # information and options. + # + # @overload find(id) + # @param id The record to find, raises an exception if the record is + # not found. + # + # @overload find(mode, options = {}) + # @param [:all,:first] mode (:all) When finding +:all+ matching records + # and array is returned of records. When finding +:first+ then + # +nil+ or a single record will be returned. + # @param [Hash] options + # @option options [Mixed] :where Conditions that determine what + # records are returned. + # @option options [String,Array] :sort The order records should be + # returned in. + # @option options [Integer] :limit The max number of records to fetch. + def find *args + _new_scope.find(*args) + end + + # Equivalent to +find(:all)+ + def all(options = {}) + find(:all, options) + end + + # Counts records in SimpleDB. + # + # With no arguments, counts all records: + # + # People.count + # + # Accepts query options to count a subset of records: + # + # People.count(:where => { :boss => true }) + # + # You can also count records on a scope object: + # + # People.find(:all).where(:boss => true).count + # + # See {#find} and {Scope#count} for more details. + # + # @param [Hash] options ({}) Options for counting + # records. + # + # @option options [Mixed] :where Conditions that determine what + # records are counted. + # @option options [Integer] :limit The max number of records to count. + def count(options = {}) + find(:all).count(options) + end + alias_method :size, :count + + # @return [Object,nil] Returns the first record found for the current + # class. If there are no records in the current classes domain, then + # nil is returned. + def first options = {} + _new_scope.find(:first, options) + end + + # Limits which records are retried from SimpleDB when performing a find. + # + # Simple string condition + # + # Car.where('color = "red" or color = "blue"').each {|car| ... } + # + # String with placeholders for quoting params + # + # Car.where('color = ?', 'red') + # + # Car.where('color = ? OR style = ?', 'red', 'compact') + # + # # produces a condition using in, like: WHERE color IN ('red', 'blue') + # Car.where('color = ?', ['red','blue']) + # + # Hash arguments + # + # # WHERE age = '40' AND gender = 'male' + # People.where(:age => 40, :gender => 'male').each {|person| ... } + # + # Chaining where with other scope modifiers + # + # # 10 most expensive red cars + # Car.where(:color => 'red').order(:price, :desc).limit(10) + # + # @overload where(conditions_hash) + # @overload where(sql_fragment[, quote_params, ...]) + # + # @param [Hash] conditions_hash A hash of attributes to values. Each + # key/value pair from the hash becomes a find condition. All conditions + # are joined by AND. + def where *args + _new_scope.where(*args) + end + + # Defines the order in which records are returned when performing a find. + # SimpleDB only allows sorting by one attribute per request. + # + # # oldest to youngest + # People.order(:age, :desc).each {|person| ... } + # + # You can chain order with the other scope modifiers: + # + # Pepole.order(:age, :desc).limit(10).each {|person| ... } + # + # @overload order(attribute, direction = :asc) + # @param [String,Symbol] attribute The attribute in SimpleDB to sort by. + # @param [:asc,:desc] direction (:asc) The direction to sort, ascending + # or descending order. + def order *args + _new_scope.order(*args) + end + + # The maximum number of records to return. By default, all records + # matching the where conditions will be returned from a find. + # + # People.limit(10).each {|person| ... } + # + # Limit can be chained with other scope modifiers: + # + # People.where(:age => 40).limit(10).each {|person| ... } + # + def limit limit + _new_scope.limit(limit) + end + + # @private + def _new_scope + Scope.new(self) + end + private :_new_scope + + # A configuration method to override the default domain name. + # @param [String] The domain name that should be used for this class. + def set_domain_name name + @_domain_name = name + end + + # @return [String] Returns the full prefixed domain name for this class. + # + def domain_name + @_domain_name ||= self.to_s + "#{Record.domain_prefix}#{@_domain_name}" + end + + # @return [AWS::SimpleDB::Domain] A reference to the domain + # this class will save data to. + def sdb_domain + AWS::SimpleDB.new.domains[domain_name] + end + + # Creates the SimpleDB domain that is configured for this class. + def create_domain + AWS::SimpleDB.new.domains.create(domain_name) + end + + # @return [Hash] A hash of Attribute objects that represent the + # "columns" objects in this domain use. + def attributes + @attributes ||= {} + end + + # @param [Symbol] name The name of the scope. Scope names should be + # method-safe and should not conflict with any of the class + # methods of {Record::Base} or {Record::Scope}. + # + # Adding a scope using built-in scope modifiers: + # + # scope :top_10, order(:rating, :desc).limit(10) + # + def scope name, scope = nil, &block + + raise ArgumentError, "only a scope or block may be passed, not both" if + scope and block_given? + + if scope + method_definition = lambda { scope } + else + method_definition = block + end + + extend(Module.new { define_method(name, &method_definition) }) + + end + + def optimistic_locking attribute_name = :version_id + attribute = integer_attr(attribute_name) + @optimistic_locking_attr = attribute + end + + # @private + def optimistic_locking_attr + @optimistic_locking_attr + end + + end + end +end diff --git a/lib/aws/record/naming.rb b/lib/aws/record/naming.rb new file mode 100644 index 00000000000..0a262b0f0aa --- /dev/null +++ b/lib/aws/record/naming.rb @@ -0,0 +1,31 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + module Record + + # @private + module Naming + + # This method should only ever get called in a Rails 3+ context + # where active model and active support have been loaded. Rails 2 + # does not call model name on object. + # @private + def model_name + @_model_name ||= + ActiveModel::Name.new(self.kind_of?(Class) ? self : self.class) + end + + end + end +end diff --git a/lib/aws/record/scope.rb b/lib/aws/record/scope.rb new file mode 100644 index 00000000000..0802563006f --- /dev/null +++ b/lib/aws/record/scope.rb @@ -0,0 +1,157 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + module Record + class Scope + + include Enumerable + + attr_reader :base_class + + def initialize base_class, options = {} + @base_class = base_class + @options = options + end + + def find id_or_mode, options = {} + + scope = _handle_options(options) + + case + when id_or_mode == :all then scope + when id_or_mode == :first then scope.limit(1).first + when scope.send(:_empty?) then base_class[id_or_mode] + else + object = scope.where('itemName() = ?', id_or_mode).limit(1).first + if object.nil? + raise RecordNotFound, "no data found for id: #{id_or_mode}" + end + object + end + + end + + def count(options = {}) + if scope = _handle_options(options) and + scope != self + scope.count + else + _item_collection.count + end + end + alias_method :size, :count + + def where *conditions + if conditions.empty? + raise ArgumentError, 'missing required condition' + end + _with(:where => Record.as_array(@options[:where]) + [conditions]) + end + + def order attribute, order = :asc + _with(:order => [attribute, order]) + end + + def limit limit + _with(:limit => limit) + end + + def each &block + if block_given? + _each_object(&block) + else + Enumerator.new(self, :"_each_object") + end + end + + # @private + def _empty? + @options == {} + end + private :_empty? + + # @private + def _each_object &block + + items = _item_collection + + items.select.each do |item_data| + obj = base_class.new + obj.send(:hydrate, item_data.name, item_data.attributes) + yield(obj) + end + + end + private :_each_object + + # @private + def _with options + Scope.new(base_class, @options.merge(options)) + end + private :_with + + # @private + def method_missing scope_name, *args + # @todo only proxy named scope methods + _merge_scope(base_class.send(scope_name, *args)) + end + private :method_missing + + # @private + def _merge_scope scope + merged = self + scope.instance_variable_get('@options').each_pair do |opt_name,opt_value| + unless [nil, []].include?(opt_value) + if opt_name == :where + opt_value.each do |condition| + merged = merged.where(*condition) + end + else + merged = merged.send(opt_name, *opt_value) + end + end + end + merged + end + private :_merge_scope + + # @private + def _handle_options(options) + scope = self + options.each_pair do |method, args| + if method == :where and args.is_a?(Hash) + # splatting a hash turns it into an array, bad juju + scope = scope.send(method, args) + else + scope = scope.send(method, *args) + end + end + scope + end + private :_handle_options + + # @private + def _item_collection + items = base_class.sdb_domain.items + items = items.order(*@options[:order]) if @options[:order] + items = items.limit(*@options[:limit]) if @options[:limit] + Record.as_array(@options[:where]).each do |where_condition| + items = items.where(*where_condition) + end + items + end + private :_item_collection + end + end +end diff --git a/lib/aws/record/validations.rb b/lib/aws/record/validations.rb new file mode 100644 index 00000000000..1745439ed48 --- /dev/null +++ b/lib/aws/record/validations.rb @@ -0,0 +1,653 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/validators/acceptance' +require 'aws/record/validators/block' +require 'aws/record/validators/confirmation' +require 'aws/record/validators/count' +require 'aws/record/validators/exclusion' +require 'aws/record/validators/format' +require 'aws/record/validators/inclusion' +require 'aws/record/validators/length' +require 'aws/record/validators/numericality' +require 'aws/record/validators/presence' + +module AWS + module Record + + # Validation methods to be used with subclasses of AWS::Record::Base. + # + # = General Usage + # + # All standard validation methods follow the same basic usage. + # Call the validation method followed by one more attribute names + # and then an optional hash of modifiers. + # + # class Book < AWS::Record::Base + # + # # ... + # + # validates_presence_of :title, :author + # + # validates_length_of :summary, + # :max => 500, + # :allow_nil => true + # + # end + # + # = Conditional Validations + # + # Sometimes you only want to validate an attribute under certain + # conditions. To make this simple, all validation methods accept the + # following 3 options: + # + # * +:on+ + # * +:if+ + # * +:unless+ + # + # You may mix and match all 3 of the above options. + # + # === Validate on :create or :update + # + # By default validations are run on create and update, but you can + # specify them to run for only create (initial save) or updates. + # + # validates_presence_of :created_at, :on => :create + # + # validates_presence_of :updated_at, :on => :update + # + # === Validate :if or :unless + # + # Sometimes you have more complex requirements to determine if/when a + # validation should run. +:if+ and +:unless+: both accept either + # a method name or proc. + # + # class Person + # + # # ... + # + # validates_presence_of :job_title, :if => :employee? + # + # validates_presence_of :nickname, :if => lambda {|person| + # person.is_family? or person.is_friend? } + # + # end + # + # = Validating Virtual (Non-persisted) Attributes + # + # All of the validators can be used with configured attributes, but they + # can also be used with any attribute that has a setter and a getter. + # + # Class Book < AWS::Record::Base + # + # attr_accessor :title + # + # validates_presence_of :title + # + # end + # + module Validations + + def self.extended base + + base.send(:define_method, :validate) do + errors.clear! + self.class.send(:validators).each do |validator| + validator.validate(self) + end + end + + base.send(:private, :validate) + + end + + # This validation method is primariliy intended for ensuring a form + # checkbox (like an EULA agreement or terms of service acknowledgement) + # is checked. + # + # class User < AWS::Record::Base + # boolean_attr :terms_of_service + # validates_acceptance_of :terms_of_service + # end + # + # === Virtual Attributes + # + # If you choose to validate the acceptance of a non-existant attribute + # then a setter and a getter will be added automtically for you. + # + # class User < AWS::Record::Base + # validates_acceptance_of :terms_of_service + # end + # + # user = User.new + # user.respond_to?(:terms_of_service) #=> true + # user.respond_to?(:terms_of_service=) #=> true + # + # === Accepted Values + # + # The default behavior for +validates_acceptance_of+ is to add + # an error when the value is '1' or +true+. Also note, this validation + # method defaults +:allow_nil+ to true. + # + # * +nil+ implies the field was omitted from the form and therefore + # should not be validated + # + # class User < AWS::Record::Base + # validates_acceptance_of :terms_of_service + # end + # + # u = User.new + # u.terms_of_service #=> nil + # u.valid? #=> true + # + # * '1' is the default value for most checkbox form helpers, and # + # therefore indicates an accepted value. + # + # * +true+ is how boolean attributes typecast '1'. This is helpful + # when you have your checkbox post its value to a +:boolean_attr+. + # + # === Multi-Valued Attributes + # + # This validator works only with single-valued attributes. If you need + # to validate that all of the values in a set are true, then use + # {#validates_inclusion_of}. + # + # @note Most validators default :allow_nil to false, this one defualts to true + # @note This validator should not be used with multi-valued attributes + # + # @overload validates_acceptance_of(*attributes, options = {}, &block) + # @param attributes A list of attribute names to validate. + # @param [Hash] options + # @option options [mixed] :accpet Specify an additional accepted value. + # + # validates_acceptance_of :agree, :accept => 'yes' + # + # @option options [String] :message A custom error message. The defualt + # +:message+ is "must be accepted". + # @option options [Boolean] :allow_nil (true) Skip validation if the + # attribute value is +nil+. + # @option options [Symbol] :on (:save) When this validation is run. + # Valid values include: + # * +:save:+ + # * +:create:+ + # * +:update:+ + # @option options [Symbol,String,Proc] :if Specifies a method or proc + # to call. The validation will only be run if the return value is + # of the method/proc is true (e.g. +:if => :name_changed?+ or + # +:if => lambda{|book| book.in_stock? }+). + # @option options [Symbol,String,Proc] :unless Specifies a method or + # proc to call. The validation will *not* be run if the return value + # is of the method/proc is false. + def validates_acceptance_of *args + validators << AcceptanceValidator.new(self, *args) + end + + # Intended primarily for validating a form field was entered correctly + # by requiring it twice: + # + # Model: + # class User < AWS::Record::Base + # validates_confirmation_of :password, :if => :password_changed? + # end + # + # View: + # <%= password_field "user", "password" %> + # <%= password_field "user", "password_confirmation" %> + # + # === Confirmation Value Accessors + # + # If your model does not have accessors for the confirmation value + # then they will be automatically added. In the example above + # the user class would have an +attr_accessor+ for + # +:password_confirmation+. + # + # === Conditional Validation + # + # Mostly commonly you only need to validate confirmation of an + # attribute when it has changed. It is therefore suggested to + # pass an +:if+ condition reflecting this: + # + # validates_confirmation_of :password, :if => :password_changed? + # + # === Multi-Valued Attributes + # + # This validator works only with single-valued attributes. + # It should not be used on attributes that have array or set values. + # + # @note This validation method does not accept the +:allow_nil+ option. + # + # @overload validates_confirmation_of(*attributes, options = {}, &block) + # @param attributes A list of attribute names to validate. + # @param [Hash] options + # @option options [String] :message A custom error message. The defualt + # +:message+ is "doesn't match confirmation". + # @option options [Symbol] :on (:save) When this validation is run. + # Valid values include: + # * +:save:+ + # * +:create:+ + # * +:update:+ + # @option options [Symbol,String,Proc] :if Specifies a method or proc + # to call. The validation will only be run if the return value is + # of the method/proc is true (e.g. +:if => :name_changed?+ or + # +:if => lambda{|book| book.in_stock? }+). + # @option options [Symbol,String,Proc] :unless Specifies a method or + # proc to call. The validation will *not* be run if the return value + # is of the method/proc is false. + def validates_confirmation_of *args + validators << ConfirmationValidator.new(self, *args) + end + + # Validates the number of values for a given attribute. + # + # === Length vs Count + # + # +validates_count_of+ validates the number of attribute values, + # whereas +validates_length_of: validates the length of each + # attribute value instead. + # + # If you need to ensure each attribute value is a given length see + # {#validates_length_of} instead. + # + # === Examples + # + # You can validate there are a certain number of values: + # + # validates_count_of :parents, :exactly => 2 + # + # You can also specify a range: + # + # validates_count_of :tags, :within => (2..10) + # + # You can also specify min and max value seperately: + # + # validates_count_of :tags, :minimum => 2, :maximum => 10 + # + # === +nil+ Values + # + # If you are validating an array or set that contains +nil+ values, + # the +nil+ values are counted normally as 1 each. + # + # If you are validating a non-enuemrable attribute that only + # contains a single nil or other scalar value, then nil is + # counted as 0. + # + # === Singular Attributes + # + # This validator is intended to for validating attributes that have + # an array or set of values. If used on an attribute that + # returns a scalar value (like +nil+ or a string), the count will + # always be 0 (for +nil+) or 1 (for everything else). + # + # It is therefore recomended to use +:validates_presence_of+ in + # place of +:validates_count_of+ when working with single-valued + # attributes. + # + # @overload validates_count_of(*attributes, options = {}, &block) + # @param attributes A list of attribute names to validate. + # @param [Hash] options + # @option options [Integer] :exactly The exact number of values the + # attribute should have. If this validation option fails the + # error message specified by +:wrong_number+ will be added. + # @option options [Range] :within An range of number of values to + # accept. If the attribute has a number of values outside this range + # then the +:too_many+ or +:too_few+ error message will be added. + # @option options [Integer] :minimum The minimum number of values + # the attribute should have. If it has fewer, the +:too_few+ error + # message will be added. + # @option options [Integer] :maximum The maximum number of values + # the attribute should have. If it has more, the +:too_many+ error + # message will be added. + # @option options [String] :too_many An error message added + # when the attribute has too many values. Defaults to + # "has too many values (maximum is %{maximum})" + # @option options [String] :too_few An error message added + # when the attribute has too few values. Defaults to + # "has too few values (minimum is %{minimum})" + # @option options [String] :wrong_number An error message + # added when the number of attribute values does not match + # the +:exactly+ option. Defaults to "has the wrong + # number of values (should have exactly %{exactly}" + # @option options [Symbol] :on (:save) When this validation is run. + # Valid values include: + # * +:save:+ + # * +:create:+ + # * +:update:+ + # @option options [Symbol,String,Proc] :if Specifies a method or proc + # to call. The validation will only be run if the return value is + # of the method/proc is true (e.g. +:if => :name_changed?+ or + # +:if => lambda{|book| book.in_stock? }+). + # @option options [Symbol,String,Proc] :unless Specifies a method or + # proc to call. The validation will *not* be run if the return value + # is of the method/proc is false. + def validates_count_of *args + validators << CountValidator.new(self, *args) + end + + # Adds a block validator that is called during record validation. + # + # class ExampleClass < AWS::Record::Base + # + # string_attr :name + # + # validates_each(:name) do |record, attribute_name, value| + # if value == 'John Doe' + # record.errors.add(attr_name, 'may not be an alias') + # end + # end + # + # end + # + # @overload validates_each(*attributes, options = {}, &block) + # @param attributes A list of attribute names to validate. + # @param [Hash] options + # @option options [Boolean] :allow_nil (false) Skip validation if the + # attribute value is +nil+. + # @option options [Symbol] :on (:save) When this validation is run. + # Valid values include: + # * +:save:+ + # * +:create:+ + # * +:update:+ + # @option options [Symbol,String,Proc] :if Specifies a method or proc + # to call. The validation will only be run if the return value is + # of the method/proc is true (e.g. +:if => :name_changed?+ or + # +:if => lambda{|book| book.in_stock? }+). + # @option options [Symbol,String,Proc] :unless Specifies a method or + # proc to call. The validation will *not* be run if the return value + # is of the method/proc is false. + def validates_each *attributes, &block + unless block_given? + raise ArgumentError, 'missing required block for validates_each' + end + validators << BlockValidator.new(self, *attributes, &block) + end + + # Validates that the attribute value is not included in the given + # enumerable. + # + # validates_exlusion_of :username, :in => %w(admin administrator) + # + # === Multi-Valued Attributes + # + # You may use this with multi-valued attributes the same way you use it + # with single-valued attributes: + # + # class Product < AWS::Record::Base + # + # string_attr :tags, :set => true + # + # validates_exlusion_of :tags, :in => four_letter_words + # + # end + # + # @overload validates_exclusion_of(*attributes, options = {}, &block) + # @param attributes A list of attribute names to validate. + # @param [Hash] options + # @option options [required, Enumerable] :in An enumerable object to + # ensure the value is not in. + # @option options [String] :message A custom error message. The defualt + # +:message+ is "is reserved". + # @option options [Boolean] :allow_nil (false) Skip validation if the + # attribute value is +nil+. + # @option options [Symbol] :on (:save) When this validation is run. + # Valid values include: + # * +:save:+ + # * +:create:+ + # * +:update:+ + # @option options [Symbol,String,Proc] :if Specifies a method or proc + # to call. The validation will only be run if the return value is + # of the method/proc is true (e.g. +:if => :name_changed?+ or + # +:if => lambda{|book| book.in_stock? }+). + # @option options [Symbol,String,Proc] :unless Specifies a method or + # proc to call. The validation will *not* be run if the return value + # is of the method/proc is false. + def validates_exclusion_of *args + validators << ExclusionValidator.new(self, *args) + end + + # Validates the attribute's value matches the given regular exression. + # + # validates_format_of :year, :with => /^\d{4}$/ + # + # You can also perform a not-match using +:without+ instead of +:with+. + # + # validates_format_of :username, :without => /\d/ + # + # === Multi-Valued Attributes + # + # You may use this with multi-valued attributes the same way you use it + # with single-valued attributes: + # + # class Product < AWS::Record::Base + # + # string_attr :tags, :set => true + # + # validates_format_of :tags, :with => /^\w{2,10}$/ + # + # end + # + # @overload validates_format_of(*attributes, options = {}, &block) + # @param attributes A list of attribute names to validate. + # @param [Hash] options + # @option options [Regexp] :with If the value matches the given + # regex, an error will not be added. + # @option options [Regexp] :without If the value matches the given + # regex, an error will be added. + # must match, or an error is added. + # @option options [String] :message A custom error message. The defualt + # +:message+ is "is reserved". + # @option options [Boolean] :allow_nil (false) Skip validation if the + # attribute value is +nil+. + # @option options [Symbol] :on (:save) When this validation is run. + # Valid values include: + # * +:save:+ + # * +:create:+ + # * +:update:+ + # @option options [Symbol,String,Proc] :if Specifies a method or proc + # to call. The validation will only be run if the return value is + # of the method/proc is true (e.g. +:if => :name_changed?+ or + # +:if => lambda{|book| book.in_stock? }+). + # @option options [Symbol,String,Proc] :unless Specifies a method or + # proc to call. The validation will *not* be run if the return value + # is of the method/proc is false. + def validates_format_of *args + validators << FormatValidator.new(self, *args) + end + + # Validates that the attribute value is included in the given enumerable + # object. + # + # class MultipleChoiceAnswer < AWS::Record::Base + # validates_inclusion_of :letter, :in => %w(a b c d e) + # end + # + # === Multi-Valued Attributes + # + # You may use this with multi-valued attributes the same way you use it + # with single-valued attributes. + # + # @overload validates_inclusion_of(*attributes, options = {}, &block) + # @param attributes A list of attribute names to validate. + # @param [Hash] options + # @option options [required, Enumerable] :in An enumerable object to + # check for the value in. + # @option options [String] :message A custom error message. The defualt + # +:message+ is "is not included in the list". + # @option options [Boolean] :allow_nil (false) Skip validation if the + # attribute value is +nil+. + # @option options [Symbol] :on (:save) When this validation is run. + # Valid values include: + # * +:save:+ + # * +:create:+ + # * +:update:+ + # @option options [Symbol,String,Proc] :if Specifies a method or proc + # to call. The validation will only be run if the return value is + # of the method/proc is true (e.g. +:if => :name_changed?+ or + # +:if => lambda{|book| book.in_stock? }+). + # @option options [Symbol,String,Proc] :unless Specifies a method or + # proc to call. The validation will *not* be run if the return value + # is of the method/proc is false. + def validates_inclusion_of *attributes + validators << InclusionValidator.new(self, *attributes) + end + + # Validates the attribute values are of a specified length. + # + # validates_lenth_of :username, :within => 3..25 + # + # === Length vs Count + # + # +validates_length_of+ validates the length of individual attribute + # values, whereas +validates_count_of: validates the number of + # attribute values. + # + # If you need to ensure there are certain number of values see + # {#validates_count_of} instead. + # + # @overload validates_length_of(*attributes, options = {}, &block) + # @param attributes A list of attribute names to validate. + # @param [Hash] options + # @option options [Enumerable] :within An enumerable object to + # ensure the length of the value falls within. + # @option options [Integer] :exactly The exact length a value must be. + # If this validation fails the error message specified by + # +:wrong_length+ will be added. + # @option options [Range] :within An enumerable object which must + # include the length of the attribute, or an error will be added. + # If the attribute has a length outside the range then the + # +:too_long+ or +:too_short+ error message will be added. + # @option options [Integer] :minimum The minimum length an attribute + # value should be. If it is shorter, the +:too_short+ error + # message will be added. + # @option options [Integer] :maximum The maximum length an attribute + # value should be. If it is longer, the +:too_long+ error + # message will be added. + # @option options [String] :too_long An error message added + # when the attribute value is too long. Defaults to + # "is too long (maximum is %{maximum} + # characters)" + # @option options [String] :too_short An error message added + # when the attribute value is too short. Defaults to + # "is too short (minimum is %{minimum} + # characters)" + # @option options [String] :wrong_length An error message + # added when the attribute has the incorrect length (as + # specified by +:exactly+). Defaults to "is the wrong + # length (should be %{exactly} characters" + # @option options [Boolean] :allow_nil (false) Skip validation if the + # attribute value is +nil+. + # @option options [Symbol] :on (:save) When this validation is run. + # Valid values include: + # * +:save:+ + # * +:create:+ + # * +:update:+ + # @option options [Symbol,String,Proc] :if Specifies a method or proc + # to call. The validation will only be run if the return value is + # of the method/proc is true (e.g. +:if => :name_changed?+ or + # +:if => lambda{|book| book.in_stock? }+). + # @option options [Symbol,String,Proc] :unless Specifies a method or + # proc to call. The validation will *not* be run if the return value + # is of the method/proc is false. + def validates_length_of *args + validators << LengthValidator.new(self, *args) + end + + # Validates the attribute has a numeric value. + # + # validates_numericality_of :age, :only_integer => true + # + # === Multi-Valued Attributes + # + # You can validate multi-valued attributes using this the same way you + # validate single-valued attributes. Each value will be validated + # individually. + # + # @overload validates_numericality_of(*attributes, options = {}, &block) + # @param attributes A list of attribute names to validate. + # @param [Hash] options + # @option options [Boolean] :only_integer (false) Adds an error + # when valiating and the value is numeric, but it not a whole number. + # @option options [Integer] :equal_to When set the value must equal + # the given value exactly. May not be used with the greater/less + # options. + # @option options [Numeric] :greater_than Ensures the attribute + # is greater than the given number. + # @option options [Integer] :greater_than_or_equal_to Ensures the + # attribute is greater than or equal to the given number. + # @option options [Numeric] :less_than Ensures the attribute is less + # than the given value. + # @option options [Integer] :less_than_or_equal_to Ensures the value is + # less than or equal to the given number. + # @option options [Numeric] :even If true, the value may only be + # an even integer. This forces the +:only_integer+ to +true+. + # @option options [Numeric] :odd If true, the value may only be + # an odd integer. This forces the +:only_integer+ to +true+. + # @option options [String] :message A custom error message. The defualt + # +:message+ is "is not a number". + # @option options [Boolean] :allow_nil (false) Skip validation if the + # attribute value is +nil+. + # @option options [Symbol] :on (:save) When this validation is run. + # Valid values include: + # * +:save:+ + # * +:create:+ + # * +:update:+ + # @option options [Symbol,String,Proc] :if Specifies a method or proc + # to call. The validation will only be run if the return value is + # of the method/proc is true (e.g. +:if => :name_changed?+ or + # +:if => lambda{|book| book.in_stock? }+). + # @option options [Symbol,String,Proc] :unless Specifies a method or + # proc to call. The validation will *not* be run if the return value + # is of the method/proc is false. + def validates_numericality_of *args + validators << NumericalityValidator.new(self, *args) + end + + # Validates the named attributes are not blank. For validation + # purposes, blank values include: + # + # * +nil+ + # * empty string + # * anything that responds to #empty? with true + # * anything that responds to #blank? with true + # + # @overload validates_presence_of(*attributes, options = {}, &block) + # @param attributes A list of attribute names to validate. + # @param [Hash] options + # @option options [String] :message A custom error message. The defualt + # +:message+ is "may not be blank". + # @option options [Symbol] :on (:save) When this validation is run. + # Valid values include: + # * +:save:+ + # * +:create:+ + # * +:update:+ + # @option options [Boolean] :allow_nil (false) Skip validation if the + # attribute value is +nil+. + # @option options [Symbol,String,Proc] :if Specifies a method or proc + # to call. The validation will only be run if the return value is + # of the method/proc is true (e.g. +:if => :name_changed?+ or + # +:if => lambda{|book| book.in_stock? }+). + # @option options [Symbol,String,Proc] :unless Specifies a method or + # proc to call. The validation will *not* be run if the return value + # is of the method/proc is false. + def validates_presence_of *args + validators << PresenceValidator.new(self, *args) + end + + # @private + private + def validators + @validators ||= [] + end + + end + end +end diff --git a/lib/aws/record/validator.rb b/lib/aws/record/validator.rb new file mode 100644 index 00000000000..65e33bf3052 --- /dev/null +++ b/lib/aws/record/validator.rb @@ -0,0 +1,237 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + module Record + + # Base class for all validators + # @private + class Validator + + # these should be defined in subclasses + ACCEPTED_OPTIONS = [] + + def initialize record_class, *attribute_names, &block + + @options = attribute_names.last.is_a?(Hash) ? attribute_names.pop : {} + + @attribute_names = attribute_names + + reject_unknown_options + + ensure_type([Symbol, Proc], :if, :unless) + ensure_is([:save, :create, :update], :on) + + setup(record_class) + + end + + # @return [String] Returns the name of the attribute this validator + # will check on the record during validation. + attr_reader :attribute_names + + # @return [Hash] Returns the hash of options for this validator. + attr_reader :options + + def validate record + if + passes_on_condition?(record) and + passes_if_condition?(record) and + passes_unless_condition?(record) + then + validate_attributes(record) + end + end + + # @private + protected + def setup klass + end + + # @private + protected + def each_value value, &block + case value + when Array, Set then value.each{|v| yield(v) } + else yield(value) + end + end + + # @private + protected + def add_accessors klass, *accessors + + methods = klass.instance_methods.collect{|m| m.to_s } + + accessors.each do |attr| + + setter = "#{attr}=" + getter = attr.to_s + + unless methods.include?(getter) + klass.send(:attr_reader, attr) + klass.send(:public, getter) + end + + unless methods.include?(setter) + klass.send(:attr_writer, attr) + klass.send(:public, setter) + end + + end + + end + + # @private + protected + def validate_attributes record + attribute_names.each do |attribute_name| + value = read_attribute_for_validation(record, attribute_name) + next if value.nil? and options[:allow_nil] + validate_attribute(record, attribute_name, value) + end + end + + # @private + protected + def read_attribute_for_validation(record, attribute_name) + record.send(attribute_name) + end + + # @private + def reject_unknown_options + invalid_keys = options.keys - self.class::ACCEPTED_OPTIONS + if invalid_keys.length == 1 + raise ArgumentError, "unknown option :#{invalid_keys.first}" + elsif invalid_keys.length > 1 + bad = invalid_keys.collect{|k| ":#{k}" }.join(', ') + raise ArgumentError, "unknown options #{bad}" + end + end + + # @private + private + def passes_if_condition? record + case options[:if] + when nil then true + when Proc then options[:if].call(record) + when String, Symbol then record.send(options[:if]) + else + raise 'invalid :if option, must be nil, a method name or a Proc' + end + end + + # @private + private + def passes_unless_condition? record + case options[:unless] + when nil then true + when Proc then !options[:unless].call(record) + when String, Symbol then !record.send(options[:unless]) + else + raise 'invalid :unless option, must be nil, a method name or a Proc' + end + end + + # @private + private + def passes_on_condition? record + case options[:on] + when nil then true + when :save then true + when :create then record.new_record? + when :update then record.persisted? + else + raise 'invalid :on option, must be nil, :save, :create or :update' + end + end + + # @private + protected + def ensure_type type_or_types, *keys + + types = Array(type_or_types) + + keys.each do |key| + + next unless options.has_key?(key) + next unless types.none?{|type| options[key].is_a?(type) } + + expected = types.map{|type| type.to_s } + if expected.count == 1 + raise ArgumentError, "expected option :#{key} to be a #{expected}" + else + msg = "expected :#{key} to be one of #{expected.join(', ')}" + raise ArgumentError, msg + end + + end + end + + def ensure_is value_or_values, *keys + values = Array(value_or_values) + keys.each do |key| + next unless options.has_key?(key) + unless values.include?(options[key]) + valid = values.map{|v| v.is_a?(Symbol) ? ":#{v}" : v.to_s }.join(', ') + raise ArgumentError, "expected :#{key} to be one of #{valid}" + end + end + end + + # @private + protected + def ensure_exclusive *key_groups + key_groups.each do |key_group| + others = key_groups - [key_group] + Array(key_group).each do |key| + next unless options.has_key?(key) + conflicts = others.flatten.select{|other| options.has_key?(other) } + unless conflicts.empty? + msg = ":#{key} may not be used with :#{conflicts.first}" + raise ArgumentError, msg + end + end + end + end + + # @private + protected + def ensure_present *keys + keys.each do |k| + unless options.has_key?(k) + raise ArgumentError, "missing required option :#{k}" + end + end + end + + # @private + protected + def ensure_at_least_one *keys + found = keys.select{|k| options.has_key?(k) } + unless found.count >= 1 + opts = keys.collect{|k| ":#{k}" }.join(', ') + msg = "must provide at least one of the following options: #{opts}" + raise ArgumentError, msg + end + end + + protected + def set_default key, value + options[key] = value unless options.has_key?(key) + end + + end + + end +end diff --git a/lib/aws/record/validators/acceptance.rb b/lib/aws/record/validators/acceptance.rb new file mode 100644 index 00000000000..b3d78d01287 --- /dev/null +++ b/lib/aws/record/validators/acceptance.rb @@ -0,0 +1,51 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/validator' + +module AWS + module Record + + # @private + class AcceptanceValidator < Validator + + ACCEPTED_OPTIONS = [:accept, :message, :allow_nil, :on, :if, :unless] + + def setup record_class + set_default(:allow_nil, true) + add_accessors(record_class, *attribute_names) + end + + def validate_attribute record, attribute_name, value + + accepted = case value + when '1' then true + when true then true + else + options.has_key?(:accept) ? + value == options[:accept] : + false + end + + record.errors.add(attribute_name, message) unless accepted + + end + + def message + options[:message] || 'must be accepted' + end + + end + + end +end diff --git a/lib/aws/record/validators/block.rb b/lib/aws/record/validators/block.rb new file mode 100644 index 00000000000..5623355aeec --- /dev/null +++ b/lib/aws/record/validators/block.rb @@ -0,0 +1,38 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/validator' + +module AWS + module Record + + # @private + class BlockValidator < Validator + + ACCEPTED_OPTIONS = [:allow_nil, :on, :if, :unless] + + def initialize *args, &block + @block = block + super(*args) + end + + attr_reader :block + + def validate_attribute record, attribute_name, value + block.call(record, attribute_name, value) + end + + end + + end +end diff --git a/lib/aws/record/validators/confirmation.rb b/lib/aws/record/validators/confirmation.rb new file mode 100644 index 00000000000..93cee4f3611 --- /dev/null +++ b/lib/aws/record/validators/confirmation.rb @@ -0,0 +1,43 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/validator' + +module AWS + module Record + + # @private + class ConfirmationValidator < Validator + + ACCEPTED_OPTIONS = [:message, :on, :if, :unless] + + def setup record_class + accessors = attribute_names.collect{|m| "#{m}_confirmation" } + add_accessors(record_class, *accessors) + end + + def validate_attribute record, attribute_name, value + confirmation_value = record.send("#{attribute_name}_confirmation") + unless value == confirmation_value + record.errors.add(attribute_name, message) + end + end + + def message + options[:message] || "doesn't match confirmation" + end + + end + + end +end diff --git a/lib/aws/record/validators/count.rb b/lib/aws/record/validators/count.rb new file mode 100644 index 00000000000..cbe17181ff0 --- /dev/null +++ b/lib/aws/record/validators/count.rb @@ -0,0 +1,108 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/validator' + +module AWS + module Record + + # @private + class CountValidator < Validator + + ACCEPTED_OPTIONS = [ + :exactly, :within, :minimum, :maximum, + :too_many, :too_few, :wrong_number, + :on, :if, :unless, + ] + + def setup record_class + ensure_at_least_one(:within, :exactly, :minimum, :maximum) + ensure_exclusive(:within, :exactly, [:minimum, :maximum]) + ensure_type(Range, :within) + ensure_type(Integer, :exactly, :minimum, :maximum) + ensure_type(String, :too_many, :too_few, :wrong_number) + end + + def validate_attribute record, attribute_name, value + + count = case value + when nil then 0 + when String then 1 + when Enumerable then value.count + else 1 + end + + if exact = options[:exactly] + unless count == exact + record.errors.add(attribute_name, wrong_number(exact, count)) + end + end + + if within = options[:within] + if count < within.first + record.errors.add(attribute_name, too_few(within.first, count)) + end + if count > within.last + record.errors.add(attribute_name, too_many(within.last, count)) + end + end + + if min = options[:minimum] + if count < min + record.errors.add(attribute_name, too_few(min, count)) + end + end + + if max = options[:maximum] + if count > max + record.errors.add(attribute_name, too_many(max, count)) + end + end + + end + + # @private + protected + def wrong_number exactly, got + msg = options[:wrong_number] || + "has the wrong number of values (should have %{exactly})" + interpolate(msg, :exactly => exactly, :count => got) + end + + # @private + protected + def too_few min, got + msg = options[:too_few] || "has too few values (minimum is %{minimum})" + interpolate(msg, :minimum => min, :count => got) + end + + # @private + protected + def too_many max, got + msg = options[:too_many] || "has too many values (maximum is %{maximum})" + interpolate(msg, :maximum => max, :count => got) + end + + protected + def interpolate message_with_placeholders, values + msg = message_with_placeholders.dup + values.each_pair do |key,value| + msg.gsub!(/%\{#{key}\}/, value.to_s) + end + msg + end + + end + + end +end diff --git a/lib/aws/record/validators/exclusion.rb b/lib/aws/record/validators/exclusion.rb new file mode 100644 index 00000000000..876aef67751 --- /dev/null +++ b/lib/aws/record/validators/exclusion.rb @@ -0,0 +1,43 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/validators/inclusion' + +module AWS + module Record + + # @private + class ExclusionValidator < InclusionValidator + + ACCEPTED_OPTIONS = [:in, :message, :allow_nil, :on, :if, :unless] + + def setup record_class + ensure_present(:in) + ensure_type(Enumerable, :in) + end + + def validate_attribute record, attribute_name, value_or_values + each_value(value_or_values) do |value| + included = value_included?(value) + record.errors.add(attribute_name, message) if included + end + end + + def message + options[:message] || 'is reserved' + end + + end + + end +end diff --git a/lib/aws/record/validators/format.rb b/lib/aws/record/validators/format.rb new file mode 100644 index 00000000000..2d29c87c24e --- /dev/null +++ b/lib/aws/record/validators/format.rb @@ -0,0 +1,57 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/validator' + +module AWS + module Record + + # @private + class FormatValidator < Validator + + ACCEPTED_OPTIONS = [ + :with, :without, + :message, :allow_nil, :on, :if, :unless, + ] + + def setup record_class + ensure_type(Regexp, :with, :without) + ensure_at_least_one(:with, :without) + end + + def validate_attribute record, attribute_name, value_or_values + each_value(value_or_values) do |value| + + if options[:with] + unless value.to_s =~ options[:with] + record.errors.add(attribute_name, message) + end + end + + if options[:without] + unless value.to_s !~ options[:without] + record.errors.add(attribute_name, message) + end + end + + end + end + + def message + options[:message] || 'is invalid' + end + + end + + end +end diff --git a/lib/aws/record/validators/inclusion.rb b/lib/aws/record/validators/inclusion.rb new file mode 100644 index 00000000000..4189000cada --- /dev/null +++ b/lib/aws/record/validators/inclusion.rb @@ -0,0 +1,56 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/validator' + +module AWS + module Record + + # @private + class InclusionValidator < Validator + + ACCEPTED_OPTIONS = [:in, :message, :allow_nil, :on, :if, :unless] + + def setup record_class + ensure_present(:in) + ensure_type(Enumerable, :in) + end + + def validate_attribute record, attribute_name, value_or_values + each_value(value_or_values) do |value| + + included = if value.is_a?(Enumerable) + value.all?{|v| value_included?(v) } + else + value_included?(value) + end + + record.errors.add(attribute_name, message) unless included + + end + end + + def value_included? value + options[:in].respond_to?(:cover?) ? + options[:in].cover?(value) : + options[:in].include?(value) + end + + def message + options[:message] || 'is not included in the list' + end + + end + + end +end diff --git a/lib/aws/record/validators/length.rb b/lib/aws/record/validators/length.rb new file mode 100644 index 00000000000..7395070029a --- /dev/null +++ b/lib/aws/record/validators/length.rb @@ -0,0 +1,107 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/validator' + +module AWS + module Record + + # @private + class LengthValidator < Validator + + ACCEPTED_OPTIONS = [ + :exactly, :within, :minimum, :maximum, + :too_long, :too_short, :wrong_length, + :allow_nil, :on, :if, :unless, + ] + + def setup record_class + ensure_at_least_one(:within, :exactly, :minimum, :maximum) + ensure_exclusive(:within, :exactly, [:minimum, :maximum]) + ensure_type(Range, :within) + ensure_type(Integer, :exactly, :minimum, :maximum) + ensure_type(String, :too_long, :too_short, :wrong_length) + end + + def validate_attribute record, attribute_name, value_or_values + each_value(value_or_values) do |value| + + length = value.respond_to?(:length) ? value.length : value.to_s.length + + if exact = options[:exactly] + unless length == exact + record.errors.add(attribute_name, wrong_length(exact, length)) + end + end + + if within = options[:within] + if length < within.first + record.errors.add(attribute_name, too_short(within.first, length)) + end + if length > within.last + record.errors.add(attribute_name, too_long(within.last, length)) + end + end + + if min = options[:minimum] + if length < min + record.errors.add(attribute_name, too_short(min, length)) + end + end + + if max = options[:maximum] + if length > max + record.errors.add(attribute_name, too_long(max, length)) + end + end + + end + end + + # @private + protected + def wrong_length exactly, got + msg = options[:wrong_length] || + "is the wrong length (should be %{exactly} characters)" + interpolate(msg, :exactly => exactly, :length => got) + end + + # @private + protected + def too_short min, got + msg = options[:too_short] || + "is too short (minimum is %{minimum} characters)" + interpolate(msg, :minimum => min, :length => got) + end + + # @private + protected + def too_long max, got + msg = options[:too_long] || + "is too long (maximum is %{maximum} characters)" + interpolate(msg, :maximum => max, :length => got) + end + + protected + def interpolate message_with_placeholders, values + msg = message_with_placeholders.dup + values.each_pair do |key,value| + msg.gsub!(/%\{#{key}\}/, value.to_s) + end + msg + end + + end + + end +end diff --git a/lib/aws/record/validators/numericality.rb b/lib/aws/record/validators/numericality.rb new file mode 100644 index 00000000000..2b29002b4d8 --- /dev/null +++ b/lib/aws/record/validators/numericality.rb @@ -0,0 +1,138 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/validator' + +module AWS + module Record + + # @private + class NumericalityValidator < Validator + + ACCEPTED_OPTIONS = [ + :greater_than, :greater_than_or_equal_to, + :less_than, :less_than_or_equal_to, + :equal_to, :only_integer, :odd, :even, + :message, :allow_nil, :on, :if, :unless, + ] + + COMPARISONS = { + :equal_to => :==, + :greater_than => :>, + :greater_than_or_equal_to => :>=, + :less_than => :<, + :less_than_or_equal_to => :<=, + :even => lambda{|value| value.to_i % 2 == 0 }, + :odd => lambda{|value| value.to_i % 2 == 1 }, + } + + def setup record_class + + ensure_exclusive(:odd, :even) + + ensure_exclusive(:equal_to, + [:greater_than, :greater_than_or_equal_to, + :less_than, :less_than_or_equal_to]) + + ensure_type([TrueClass, FalseClass], :only_integer) + + ensure_type(TrueClass, :odd, :even) + + ensure_type([Numeric, Symbol, Proc], + :greater_than, :greater_than_or_equal_to, + :less_than, :less_than_or_equal_to, + :equal_to) + + end + + def read_attribute_for_validation(record, attribute_name) + if record.respond_to?("#{attribute_name}_before_type_cast") + record.send("#{attribute_name}_before_type_cast") + else + record.send(attribute_name) + end + end + + def validate_attribute record, attribute_name, raw + each_value(raw) do |raw_value| + + if options[:only_integer] or options[:odd] or options[:even] + value = as_integer(raw_value) + error_type = :not_an_integer + else + value = as_number(raw_value) + error_type = :not_a_number + end + + unless value + record.errors.add(attribute_name, message_for(error_type)) + return + end + + COMPARISONS.each do |option,method| + + next unless options.has_key?(option) + + requirement = case options[option] + when Symbol then record.send(options[option]) + when Proc then options[option].call(record) + else options[option] + end + + valid = case method + when Symbol then value.send(method, requirement) + else method.call(value) + end + + unless valid + message = message_for(option, requirement) + record.errors.add(attribute_name, message) + end + + end + end + end + + def message_for error_type, requirement = nil + return options[:message] if options[:message] + case error_type + when :not_a_number then 'is not a number' + when :not_an_integer then 'must be an integer' + when :even then 'must be an even number' + when :odd then 'must be an odd number' + when :equal_to then "should equal #{requirement}" + else + "must be #{error_type.to_s.gsub(/_/, ' ')} #{requirement}" + end + end + + def as_number value + begin + Kernel.Float(value) + rescue ArgumentError, TypeError + nil + end + end + + def as_integer value + begin + Kernel.Integer(value) + rescue ArgumentError, TypeError + nil + end + end + + end + + end +end diff --git a/lib/aws/record/validators/presence.rb b/lib/aws/record/validators/presence.rb new file mode 100644 index 00000000000..0ce82ca17e5 --- /dev/null +++ b/lib/aws/record/validators/presence.rb @@ -0,0 +1,45 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/record/validator' + +module AWS + module Record + + # @private + class PresenceValidator < Validator + + ACCEPTED_OPTIONS = [:message, :allow_nil, :on, :if, :unless] + + def validate_attribute record, attribute_name, value + + blank = case + when value.nil? then true + when value.is_a?(String) then value !~ /\S/ + when value == false then false # defeat false.blank? == true + when value.respond_to?(:empty?) then value.empty? + when value.respond_to?(:blank?) then value.blank? + else false + end + + record.errors.add(attribute_name, message) if blank + + end + + def message + options[:message] || 'may not be blank' + end + + end + end +end diff --git a/lib/aws/resource_cache.rb b/lib/aws/resource_cache.rb new file mode 100644 index 00000000000..24570d78227 --- /dev/null +++ b/lib/aws/resource_cache.rb @@ -0,0 +1,39 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + + # @private + class ResourceCache + + def initialize + @cache = {} + end + + def store(key, attributes) + (@cache[key] ||= {}).merge!(attributes) + end + + def cached?(key, attribute) + attributes = @cache[key] and + attributes.key?(attribute) + end + + def get(key, attribute) + raise "No cached value for attribute :#{attribute} of #{key}" unless + cached?(key, attribute) + @cache[key][attribute] + end + + end +end diff --git a/lib/aws/response.rb b/lib/aws/response.rb new file mode 100644 index 00000000000..50be1ea1d8e --- /dev/null +++ b/lib/aws/response.rb @@ -0,0 +1,113 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/async_handle' + +module AWS + + # @private + class Response + + include AsyncHandle + + # @return [AWS::Error] Returns nil unless the request failed. + # Normally this will be nil unless you are using the Asynchronous + # interface. + attr_accessor :error + + # @return [Hash] The hash of options passed to the low level request + # method that generated this response. + attr_accessor :request_options + + # @return [Symbol] The low-level request method that generated + # this response + attr_accessor :request_type + + # @return [Http::Request] the HTTP request object + attr_accessor :http_request + + # @return [Http::Response] the HTTP response object + attr_accessor :http_response + + # @return [Boolean] true if the response is cached + attr_accessor :cached + + # @param [Http::Request] http_request + # @param [Http::Response] http_request + def initialize http_request = nil, http_response = nil + @http_request = http_request + @http_response = http_response + end + + # @return [Boolean] Teturns true unless there is a response error. + def successful? + error.nil? + end + + # @return [Boolean] Returns true if the http request was throttled + # by AWS. + def throttled? + !successful? and + parsed_body = XmlGrammar.parse(http_response.body) and + parsed_body.respond_to?(:code) and + parsed_body.code == "Throttling" + end + + # @return [Boolean] Returns true if the http request timed out. + def timeout? + http_response.timeout? + end + + # @private + def inspect + "<#{self.class}>" + end + + def cache_key + [http_request.access_key_id, + http_request.host, + request_type, + serialized_options].join(":") + end + + def serialized_options + serialize_options_hash(request_options) + end + + private + def serialize_options_hash(hash) + "(" + hash.keys.sort_by(&:to_s).map do |key| + "#{key}=#{serialize_options_value(hash[key])}" + end.join(" ") + ")" + end + + private + def serialize_options_value(value) + case value + when Hash + serialize_options_hash(value) + when Array + serialize_options_array(value) + else + value.inspect + end + end + + private + def serialize_options_array(ary) + "[" + ary.map { |v| serialize_options_value(v) }.join(" ") + + "]" + end + + end +end diff --git a/lib/aws/response_cache.rb b/lib/aws/response_cache.rb new file mode 100644 index 00000000000..3091f609643 --- /dev/null +++ b/lib/aws/response_cache.rb @@ -0,0 +1,50 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/resource_cache' + +module AWS + + # @private + class ResponseCache + + attr_reader :cached_responses + + attr_reader :resource_cache + + def initialize + @cached_responses = [] + @indexed_responses = {} + @resource_cache = ResourceCache.new + end + + def add(resp) + cached_responses.unshift(resp) + @indexed_responses[resp.cache_key] = resp if + resp.respond_to?(:cache_key) + @resource_cache = ResourceCache.new + end + + def select(*types, &block) + cached_responses.select do |resp| + types.map { |t| t.to_s }.include?(resp.request_type.to_s) and + (block.nil? || block.call(resp)) + end + end + + def cached(resp) + @indexed_responses[resp.cache_key] + end + + end +end diff --git a/lib/aws/s3.rb b/lib/aws/s3.rb new file mode 100644 index 00000000000..852d19e16c8 --- /dev/null +++ b/lib/aws/s3.rb @@ -0,0 +1,109 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/common' +require 'aws/service_interface' +require 'aws/s3/client' +require 'aws/s3/errors' +require 'aws/s3/bucket_collection' + +module AWS + + # Provides an expressive, object-oriented interface to Amazon S3. + # + # To use Amazon S3 you must first + # {sign up here}[http://aws.amazon.com/s3/]. + # + # For more information about Amazon S3, see: + # + # * {Amazon S3}[http://aws.amazon.com/s3/] + # * {Amazon S3 Documentation}[http://aws.amazon.com/documentation/s3/] + # + # == Credentials + # + # You can setup default credentials for all AWS services via + # AWS.config: + # + # AWS.config( + # :access_key_id => 'YOUR_ACCESS_KEY_ID', + # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') + # + # Or you can set them directly on the S3 interface: + # + # s3 = AWS::S3.new( + # :access_key_id => 'YOUR_ACCESS_KEY_ID', + # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') + # + # == Buckets Keys and Objects + # + # S3 stores objects in buckets. + # + # To create a bucket: + # + # bucket = s3.buckets.create('mybucket') + # + # To get a bucket: + # + # bucket = s3.buckets[:mybucket] + # bucket = s3.buckets['mybucket'] + # + # Listing buckets: + # + # s3.buckets.each do |bucket| + # puts bucket.name + # end + # + # See {Bucket} and {BucketCollection} for more information on working + # with S3 buckets. + # + # == Listing Objects + # + # Enumerating objects in a bucket: + # + # bucket.objects.each do |object| + # puts object.key #=> no data is fetched from s3, just a list of keys + # end + # + # You can limit the list of objects with a prefix, or explore the objects + # in a bucket as a tree. See {ObjectCollection} for more information. + # + # == Reading and Writing to S3 + # + # Each object in a bucket has a unique key. + # + # photo = s3.buckets['mybucket'].objects['photo.jpg'] + # + # Writing to an S3Object: + # + # photo.write(File.read('/some/photo.jpg')) + # + # Reading from an S3Object: + # + # File.open("/some/path/on/disk.jpg", "w") do |f| + # f.write(photo.read) + # end + # + # See {S3Object} for more information on reading and writing to S3. + # + class S3 + + include ServiceInterface + + # @return [BucketCollection] Returns a collection that represents all + # buckets in the account. + def buckets + BucketCollection.new(:config => @config) + end + + end +end diff --git a/lib/aws/s3/access_control_list.rb b/lib/aws/s3/access_control_list.rb new file mode 100644 index 00000000000..7ce7d5b6ecd --- /dev/null +++ b/lib/aws/s3/access_control_list.rb @@ -0,0 +1,252 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/s3/acl_object' + +module AWS + class S3 + + # Represents an access control list for S3 objects and buckets. For example: + # + # acl = AccessControlList.new + # acl.grant(:full_control). + # to(:canonical_user_id => "8a6925ce4adf588a4f21c32aa379004fef") + # acl.to_xml # => '...' + # + # You can also construct an AccessControlList from a hash: + # + # AccessControlList.new( + # :owner => { :id => "8a6925ce4adf588a4f21c32aa379004fef" }, + # :grants => [{ :grantee => { + # :canonical_user_id => "8a6925ce4adf588a4f21c32aa379004fef", + # }, + # :permission => :full_control }] + # ) + # + # @see ACLObject + # + # @attr [AccessControlList::Owner] owner The owner of the access + # control list. You can set this as a hash, for example: + # acl.owner = { :id => '8a6925ce4adf588a4f21c32aa379004fef' } + # This attribute is required when setting an ACL. + # + # @attr [list of AccessControlList::Grant] grants The list of + # grants. You can set this as a list of hashes, for example: + # acl.grants = [{ :grantee => { :canonical_user_id => + # "8a6925ce4adf588a4f21c32aa379004fef" }, + # :permission => :full_control }] + class AccessControlList + + # Represents an ACL owner. In the default ACL, this is the + # bucket owner. + # + # @attr [String] id The canonical user ID of the ACL owner. + # This attribute is required when setting an ACL. + # + # @attr [String] display_name The display name of the ACL + # owner. This value is ignored when setting an ACL. + class Owner + include ACLObject + + string_attr "ID", :required => true + string_attr "DisplayName" + end + + # Represents a user who is granted some kind of permission + # through a Grant. There are three ways to specify a grantee: + # + # * You can specify the canonical user ID, for example. When + # you read an ACL from S3, all grantees will be identified + # this way, and the display_name attribute will also be provided. + # + # Grantee.new(:canonical_user_id => "8a6925ce4adf588a4f21c32aa379004fef") + # + # * You can specify the e-mail address of an AWS customer, for example: + # Grantee.new(:amazon_customer_email => 'foo@example.com') + # + # * You can specify a group URI, for example: + # Grantee.new(:group_uri => 'http://acs.amazonaws.com/groups/global/AllUsers') + # For more details about group URIs, see: + # http://docs.amazonwebservices.com/AmazonS3/latest/dev/ACLOverview.html + # + # When constructing a grantee, you must provide a value for + # exactly one of the following attributes: + # + # * +amazon_customer_email+ + # * +canonical_user_id+ + # * +group_uri+ + # + # @attr [String] amazon_customer_email The e-mail address of + # an AWS customer. + # + # @attr [String] canonical_user_id The canonical user ID of an + # AWS customer. + # + # @attr [String] group_uri A URI that identifies a particular + # group of users. + # + # @attr [String] display_name The display name associated with + # the grantee. This is provided by S3 when reading an ACL. + class Grantee + include ACLObject + + SIGNAL_ATTRIBUTES = [:amazon_customer_email, + :canonical_user_id, + :group_uri] + + string_attr "EmailAddress", :method_name => "amazon_customer_email" + string_attr "ID", :method_name => "canonical_user_id" + string_attr "URI", :method_name => "group_uri" + string_attr "DisplayName" + + # (see ACLObject#validate!) + def validate! + attr = signal_attribute + raise "missing amazon_customer_email, canonical_user_id, "+ + "or group_uri" unless attr + raise "display_name is invalid with #{attr}" if + attr != :canonical_user_id and display_name + end + + # @private + def stag + if attr = signal_attribute + super + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" + + " xsi:type=\"#{type_for_attr(attr)}\"" + else + super + end + end + + # @private + def signal_attribute + SIGNAL_ATTRIBUTES.find { |att| send(att) } + end + + # @private + def type_for_attr(attr) + { :amazon_customer_email => "AmazonCustomerByEmail", + :canonical_user_id => "CanonicalUser", + :group_uri => "Group" }[attr] + end + + end + + # Represents the permission being granted in a Grant object. + # Typically you will not need to construct an instance of this + # class directly. + # @see Grant#permission + class Permission + include ACLObject + + # The permission expressed as a symbol following Ruby + # conventions. For example, S3's FULL_CONTROL permission + # will be returned as +:full_control+. + attr_reader :name + + # @private + def initialize(name) + raise "expected string or symbol" unless + name.respond_to?(:to_str) or name.respond_to?(:to_sym) + @name = name.to_sym + end + + def body_xml + name.to_s.upcase + end + + end + + # Represents a single grant in an ACL. Both +grantee+ and + # +permission+ are required for each grant when setting an + # ACL. + # + # See + # http://docs.amazonwebservices.com/AmazonS3/latest/dev/ACLOverview.html + # for more information on how grantees and permissions are + # interpreted by S3. + # + # @attr [Grantee] grantee The user or users who are granted + # access according to this grant. You can specify this as a + # hash: + # grant.grantee = { :amazon_customer_email => "foo@example.com" } + # + # @attr [Permission or Symbol] permission The type of + # permission that is granted by this grant. Valid values are: + # * +:read+ + # * +:write+ + # * +:read_acp+ + # * +:write_acp+ + # * +:full_control+ + class Grant + + include ACLObject + + object_attr Grantee, :required => true + object_attr Permission, :required => true, :cast => Symbol + + end + + include ACLObject + + # @private + def stag + super()+" xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"" + end + + # @private + def element_name + "AccessControlPolicy" + end + + class GrantBuilder + + # @private + def initialize(acl, grant) + @acl = acl + @grant = grant + end + + # Specifies the grantee. + # + # @param [Grantee or Hash] grantee A Grantee object or hash; + # for example: + # acl.grant(:full_control).to(:amazon_customer_email => "foo@example.com") + def to(grantee) + @grant.grantee = grantee + @acl.grants << @grant + end + + end + + # Convenience method for constructing a new grant and adding + # it to the ACL. Example usage: + # + # acl.grants.size # => 0 + # acl.grant(:full_control). + # to(:canonical_user_id => "8a6925ce4adf588a4f21c32aa379004fef") + # acl.grants.size # => 1 + # + # @return [GrantBuilder] + def grant(permission) + GrantBuilder.new(self, Grant.new(:permission => permission)) + end + + object_attr Owner, :required => true + object_list_attr("AccessControlList", Grant, + :required => true, :method_name => :grants) + + end + + end +end diff --git a/lib/aws/s3/acl_object.rb b/lib/aws/s3/acl_object.rb new file mode 100644 index 00000000000..bcd1bb26599 --- /dev/null +++ b/lib/aws/s3/acl_object.rb @@ -0,0 +1,266 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/meta_utils' +require 'aws/inflection' +require 'rexml/text' + +module AWS + class S3 + + # Common methods for AccessControlList and related objects. + module ACLObject + + # @private + def initialize(opts = {}); end + + # @private + def body_xml; ""; end + + # @private + def stag + element_name + end + + # @private + def element_name + self.class.name[/::([^:]*)$/, 1] + end + + # Returns the XML representation of the object. Generally + # you'll want to call this on an AccessControlList object, + # which will yield an XML representation of the ACL that you + # can send to S3. + def to_s + if body_xml.empty? + "<#{stag}/>" + else + "<#{stag}>#{body_xml}" + end + end + + # (see #to_s) + def to_xml + to_s + end + + # Returns true if and only if this object is valid according + # to S3's published ACL schema. In particular, this will + # check that all required attributes are provided and that + # they are of the correct type. + def valid? + validate! + rescue => e + false + else + true + end + + # Raises an exception unless this object is valid according to + # S3's published ACL schema. + # @see #valid? + def validate!; end + + # @private + def validate_input(name, value, context = nil) + send("validate_#{name}_input!", value, context) + end + + # @private + module ClassMethods + + def string_attr(element_name, options = {}) + method_name = options[:method_name] || + Inflection.ruby_name(element_name) + + attr_accessor(method_name) + setter_option(method_name) + string_input_validator(method_name) + validate_string(method_name) if options[:required] + body_xml_string_content(element_name, method_name) + end + + def object_attr(klass, options = {}) + base_name = klass.name[/::([^:]*)$/, 1] + method_name = Inflection.ruby_name(base_name) + cast = options[:cast] || Hash + + attr_reader(method_name) + setter_option(method_name) + object_setter(klass, method_name, cast) + object_input_validator(klass, base_name, method_name, cast) + validate_object(method_name) if options[:required] + body_xml_content(method_name) + end + + def object_list_attr(list_element, klass, options = {}) + base_name = klass.name[/::([^:]*)$/, 1] + method_name = Inflection.ruby_name(options[:method_name].to_s || list_element) + default_value = nil + default_value = [] if options[:required] + + attr_reader(method_name) + setter_option(method_name) { [] if options[:required] } + object_list_setter(klass, method_name) + object_list_input_validator(klass, base_name, method_name) + validate_list(method_name) + body_xml_list_content(list_element, method_name) + end + + def setter_option(method_name) + MetaUtils.class_extend_method(self, :initialize) do |*args| + opts = args.last || {} + instance_variable_set("@#{method_name}", yield) if block_given? + key = method_name.to_sym + + if opts.has_key?(key) + value = opts[key] + validate_input(method_name, value, "for #{method_name} option") + self.send("#{method_name}=", value) + end + super(opts) + end + end + + def string_input_validator(method_name) + input_validator(method_name) do |value, context| + raise ArgumentError.new("expected string"+context) unless + value.respond_to?(:to_str) + end + end + + def object_input_validator(klass, base_name, method_name, cast) + input_validator(method_name) do |value, context| + if value.kind_of?(cast) + klass.new(value).validate! + else + raise ArgumentError.new("expected #{base_name} object or hash"+context) unless + value.nil? or value.kind_of? klass + end + end + end + + def object_list_input_validator(klass, base_name, method_name) + input_validator(method_name) do |value, context| + raise ArgumentError.new("expected array"+context) unless value.kind_of?(Array) + value.each do |member| + if member.kind_of?(Hash) + klass.new(member).validate! + else + raise ArgumentError.new("expected array#{context} "+ + "to contain #{base_name} objects "+ + "or hashes") unless + member.kind_of? klass + end + end + end + end + + def input_validator(method_name, &blk) + validator = "__validator__#{blk.object_id}" + MetaUtils.class_extend_method(self, validator, &blk) + MetaUtils.class_extend_method(self, "validate_#{method_name}_input!") do |*args| + (value, context) = args + context = " "+context if context + context ||= "" + send(validator, value, context) + end + end + + def object_setter(klass, method_name, cast) + define_method("#{method_name}=") do |value| + validate_input(method_name, value) + if value.kind_of?(cast) + value = klass.new(value) + end + instance_variable_set("@#{method_name}", value) + end + end + + def object_list_setter(klass, method_name) + define_method("#{method_name}=") do |value| + validate_input(method_name, value) + list = value.map do |member| + if member.kind_of?(Hash) + klass.new(member) + else + member + end + end + instance_variable_set("@#{method_name}", list) + end + end + + def validate_string(method_name) + MetaUtils.class_extend_method(self, :validate!) do + super() + raise "missing #{method_name}" unless send(method_name) + end + end + + def validate_object(method_name) + MetaUtils.class_extend_method(self, :validate!) do + super() + raise "missing #{method_name}" unless send(method_name) + send(method_name).validate! + end + end + + def validate_list(method_name) + MetaUtils.class_extend_method(self, :validate!) do + super() + raise "missing #{method_name}" unless send(method_name) + send(method_name).each { |member| member.validate! } + end + end + + def body_xml_string_content(element_name, method_name) + add_xml_child(method_name) do |value| + normalized = REXML::Text.normalize(value.to_s) + "<#{element_name}>#{normalized}" + end + end + + def body_xml_content(method_name) + add_xml_child(method_name) { |value| value.to_s } + end + + def body_xml_list_content(list_element, method_name) + add_xml_child(method_name) do |list| + list_content = list.map { |member| member.to_s }.join + if list_content.empty? + "<#{list_element}/>" + else + "<#{list_element}>#{list_content}" + end + end + end + + def add_xml_child(method_name) + MetaUtils.class_extend_method(self, :body_xml) do + xml = super() + value = send(method_name) + xml += yield(value) if value + xml + end + end + + end + + def self.included(m) + m.extend(ClassMethods) + end + + end + end +end diff --git a/lib/aws/s3/bucket.rb b/lib/aws/s3/bucket.rb new file mode 100644 index 00000000000..0247c71ed0b --- /dev/null +++ b/lib/aws/s3/bucket.rb @@ -0,0 +1,320 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/object_collection' +require 'aws/s3/bucket_version_collection' +require 'aws/s3/object_version_collection' +require 'aws/s3/multipart_upload_collection' +require 'aws/s3/tree' +require 'aws/meta_utils' + +module AWS + class S3 + + # Represents a single S3 bucket. + # + # @example Creating a Bucket + # + # bucket = s3.buckets.create('mybucket') + # + # @example Getting an Existing Bucket + # + # bucket = s3.buckets['mybucket'] + # + class Bucket + + include Model + + # @param [String] name + # @param [Hash] options + # @option options [String] :owner (nil) The owner id of this bucket. + def initialize(name, options = {}) + # the S3 docs disagree with what the service allows, + # so it's not safe to toss out invalid bucket names + # S3::Client.validate_bucket_name!(name) + @name = name + @owner = options[:owner] + super + end + + # @return [String] The bucket name + attr_reader :name + + # Returns the url for this bucket. + # @return [String] url to the bucket + def url + if client.dns_compatible_bucket_name?(name) + "http://#{name}.s3.amazonaws.com/" + else + "http://s3.amazonaws.com/#{name}/" + end + end + + # @return [Boolean] Returns true if the bucket has no objects + # (this includes versioned objects that are delete markers). + def empty? + versions.first ? false : true + end + + # @return [String,nil] Returns the location constraint for a bucket + # (if it has one), nil otherwise. + def location_constraint + client.get_bucket_location(:bucket_name => name).location_constraint + end + + # Enables versioning on this bucket. + # @return [nil] + def enable_versioning + client.set_bucket_versioning( + :bucket_name => @name, + :state => :enabled) + nil + end + + # Suspends versioning on this bucket. + # @return [nil] + def suspend_versioning + client.set_bucket_versioning( + :bucket_name => @name, + :state => :suspended) + nil + end + + # @return [Boolean] returns +true+ if version is enabled on this bucket. + def versioning_enabled? + versioning_state == :enabled + end + alias_method :versioned?, :versioning_enabled? + + # Returns the versioning status for this bucket. States include: + # + # * +:enabled+ - currently enabled + # * +:suspended+ - currently suspended + # * +:unversioned+ - versioning has never been enabled + # + # @return [Symbol] the versioning state + def versioning_state + client.get_bucket_versioning(:bucket_name => @name).status + end + + # Deletes the current bucket. + # + # @note the bucket *must* be empty. + # @return [nil] + def delete + client.delete_bucket(:bucket_name => @name) + nil + end + + # Deletes any objects and versions which may be in the bucket, + # then deletes the bucket. + # @return [nil] + def delete! + versions.each{|version| version.delete } + delete + end + + # @return [String] bucket owner id + def owner + @owner || client.list_buckets.owner + end + + # @private + def inspect + "#" + end + + # @return [Boolean] Returns true if the two buckets have the same name. + def ==(other) + other.kind_of?(Bucket) && other.name == name + end + + # @return [Boolean] Returns true if the two buckets have the same name + def eql?(other_bucket) + self == other_bucket + end + + # @note This method only indicates if there is a bucket in S3, not + # if you have permissions to work with the bucket or not. + # @return [Boolean] Returns true if the bucket exists in S3. + def exists? + begin + versioned? # makes a get bucket request without listing contents + # raises a client error if the bucket doesn't exist or + # if you don't have permission to get the bucket + # versioning status. + true + rescue Errors::NoSuchBucket => e + false # bucket does not exist + rescue Errors::ClientError => e + true # bucket exists + end + end + + # @return [ObjectCollection] Represents all objects(keys) in + # this bucket. + def objects + ObjectCollection.new(self) + end + + # @return [BucketVersionCollection] Represents all of the versioned + # objects stored in this bucket. + def versions + BucketVersionCollection.new(self) + end + + # @return [MultipartUploadCollection] Represents all of the + # multipart uploads that are in progress for this bucket. + def multipart_uploads + MultipartUploadCollection.new(self) + end + + # @private + module ACLProxy + + attr_accessor :bucket + + def change + yield(self) + bucket.acl = self + end + + end + + # Returns the bucket's access control list. This will be an + # instance of AccessControlList, plus an additional +change+ + # method: + # + # bucket.acl.change do |acl| + # acl.grants.reject! do |g| + # g.grantee.canonical_user_id != bucket.owner.id + # end + # end + # + # @return [AccessControlList] + def acl + acl = client.get_bucket_acl(:bucket_name => name).acl + acl.extend ACLProxy + acl.bucket = self + acl + end + + # Sets the bucket's access control list. +acl+ can be: + # + # * An XML policy as a string (which is passed to S3 uninterpreted) + # * An AccessControlList object + # * Any object that responds to +to_xml+ + # * Any Hash that is acceptable as an argument to + # AccessControlList#initialize. + # + # @param [AccessControlList] acl + # @return [nil] + def acl=(acl) + client.set_bucket_acl(:bucket_name => name, :acl => acl) + nil + end + + # @private + module PolicyProxy + + attr_accessor :bucket + + def change + yield(self) + bucket.policy = self + end + + def delete + bucket.client.delete_bucket_policy(:bucket_name => bucket.name) + end + + end + + # Returns the bucket policy. This will be an instance of + # Policy. The returned policy will also have the methods of + # PolicyProxy mixed in, so you can use it to change the + # current policy or delete it, for example: + # + # if policy = bucket.policy + # # add a statement + # policy.change do |p| + # p.allow(...) + # end + # + # # delete the policy + # policy.delete + # end + # + # Note that changing the policy is not an atomic operation; it + # fetches the current policy, yields it to the block, and then + # sets it again. Therefore, it's possible that you may + # overwrite a concurrent update to the policy using this + # method. + # + # @return [Policy,nil] Returns the bucket policy (if it has one), + # or it returns +nil+ otherwise. + def policy + policy = client.get_bucket_policy(:bucket_name => name).policy + policy.extend(PolicyProxy) + policy.bucket = self + policy + rescue Errors::NoSuchBucketPolicy => e + nil + end + + # Sets the bucket's policy. + # + # @param policy The new policy. This can be a string (which + # is assumed to contain a valid policy expressed in JSON), a + # Policy object or any object that responds to +to_json+. + # @see Policy + # @return [nil] + def policy=(policy) + client.set_bucket_policy(:bucket_name => name, :policy => policy) + nil + end + + # Returns a tree that allows you to expose the bucket contents + # like a directory structure. + # + # @see Tree + # @param [Hash] options + # @option options [String] :prefix (nil) Set prefix to choose where + # the top of the tree will be. A value of +nil+ means + # that the tree will include all objects in the collection. + # + # @option options [String] :delimiter ('/') The string that separates + # each level of the tree. This is usually a directory separator. + # + # @option options [Boolean] :append (true) If true, the delimiter is + # appended to the prefix when the prefix does not already end + # with the delimiter. + # + # @return [Tree] + def as_tree options = {} + objects.as_tree(options) + end + + # Generates fields for a presigned POST to this object. All + # options are sent to the PresignedPost constructor. + # + # @see PresignedPost + def presigned_post(options = {}) + PresignedPost.new(self, options) + end + + end + + end +end diff --git a/lib/aws/s3/bucket_collection.rb b/lib/aws/s3/bucket_collection.rb new file mode 100644 index 00000000000..84f61f9c34d --- /dev/null +++ b/lib/aws/s3/bucket_collection.rb @@ -0,0 +1,122 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/bucket' + +module AWS + class S3 + + # Represents a collection of buckets. + # + # You can use this to create a bucket: + # + # s3.buckets.create(:name => "mybucket") + # + # You can get a handle for a specific bucket with indifferent + # access: + # + # bucket = s3.buckets[:mybucket] + # bucket = s3.buckets['mybucket'] + # + # You can also use it to find out which buckets are in your account: + # + # s3.buckets.collect(&:name) + # #=> ['bucket1', 'bucket2', ...] + # + class BucketCollection + + include Model + include Enumerable + + # Creates and returns a new Bucket. For example: + # + # @example + # + # bucket = s3.buckets.create('my-bucket') + # bucket.name #=> "my-bucket" + # bucket.exists? #=> true + # + # @param [String] bucket_name + # @param [Hash] options + # @option options [String] :location_constraint (nil) The location + # where the bucket should be created. Defaults to the classic + # US region. + # @option options [String] :acl (:private) Sets the ACL of the bucket + # you are creating. Valid Values include :private, :public_read, + # :public_read_write, :authenticated_read, :bucket_owner_read and + # :bucket_owner_full_control + # @return [Bucket] + def create(bucket_name, options = {}) + + # auto set the location constraint for the user if it is not + # passed in and the endpoint is not the us-standard region. don't + # override the location constraint though, even it is wrong, + unless + config.s3_endpoint == 's3.amazonaws.com' or + options[:location_constraint] + then + options[:location_constraint] = case config.s3_endpoint + when 's3-eu-west-1.amazonaws.com' then 'EU' + else config.s3_endpoint.match(/^s3-(.*)\.amazonaws\.com$/)[1] + end + end + + client.create_bucket(options.merge(:bucket_name => bucket_name)) + bucket_named(bucket_name) + + end + + # Returns the Bucket with the given name. + # + # Makes no requests. The returned bucket object can + # be used to make requets for the bucket and its objects. + # + # @example + # + # bucket = s3.buckets[:mybucket], + # bucket = s3.buckets['mybucket'], + # + # @param [String] bucket_name + # @return [Bucket] + def [] bucket_name + bucket_named(bucket_name) + end + + # Iterates the buckets in this collection. + # + # @example + # + # s3.buckets.each do |bucket| + # puts bucket.name + # end + # + # @return [nil] + def each &block + response = client.list_buckets + response.buckets.each do |b| + yield(bucket_named(b.name, response.owner)) + end + nil + end + + # @private + private + def bucket_named name, owner = nil + S3::Bucket.new(name.to_s, :owner => owner, :config => config) + end + + end + + end +end diff --git a/lib/aws/s3/bucket_version_collection.rb b/lib/aws/s3/bucket_version_collection.rb new file mode 100644 index 00000000000..53a2d784cd0 --- /dev/null +++ b/lib/aws/s3/bucket_version_collection.rb @@ -0,0 +1,85 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/object_version' +require 'aws/s3/prefix_and_delimiter_collection' + +module AWS + class S3 + + # A collection of versioned objects for the entire bucket. + # + # @see PrefixedCollection + class BucketVersionCollection + + include Model + include Enumerable + include PrefixAndDelimiterCollection + + # @param [Bucket] bucket + def initialize bucket, options = {} + @bucket = bucket + super + end + + # @return [Bucket] The bucket this collection belongs to. + attr_reader :bucket + + # @return [ObjectVersion] Returns the most recently created object + # version in the entire bucket. + def latest + self.find{|version| true } + end + + # Yields once for each version in the bucket. + # + # @yield [object_version] + # @yieldparam [ObjectVersion] object_version + # @return nil + def each options = {}, █ super; end + + # @private + protected + def each_member_in_page(page, &block) + super + page.versions.each do |version| + object_version = + ObjectVersion.new(bucket.objects[version.key], + version.version_id, + :delete_marker => version.delete_marker?) + yield(object_version) + end + end + + # @private + protected + def list_request(options) + client.list_object_versions(options) + end + + # @private + protected + def limit_param; :max_keys; end + + # @private + protected + def pagination_markers; super + [:version_id_marker]; end + + # @private + protected + def page_size(resp); super + resp.versions.size; end + + end + end +end diff --git a/lib/aws/s3/client.rb b/lib/aws/s3/client.rb new file mode 100644 index 00000000000..728c8ac83b6 --- /dev/null +++ b/lib/aws/s3/client.rb @@ -0,0 +1,999 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/s3/request' +require 'aws/http/response' +require 'aws/base_client' +require 'aws/s3/errors' +require 'aws/s3/data_options' +require 'aws/s3/access_control_list' +require 'aws/s3/policy' +require 'aws/s3/client/xml' +require 'pathname' +require 'stringio' +require 'json' +require 'rexml/document' + +module AWS + class S3 + + ## + # Provides a low-level client to Amazon S3: + # + # * Each method makes exactly one request to S3, and no two + # methods make the same type of request. + # + # * These methods hide the details of how request parameters are + # sent to S3; for example: + # + # client.set_bucket_acl(# controls which host to connect to + # :bucket_name => "mybucket", + # # the request payload + # :acl => [{ :grantee => "..." }]) + # + # * These methods return subclasses of Response, so that you can + # always get access to the request that was made and the raw + # HTTP response. You can also access S3-specific response + # metadata. For example: + # + # response = client.list_buckets + # response.http_request.http_method # => "GET" + # response.http_response.body # => " "32FE2CEB32F5EE25" + # # (S3-specific metadata) + # + # * This client attempts to raise ArgumentError for any invalid + # requests it can detect before sending a request to the + # service. For example: + # + # begin + # client.create_bucket + # rescue ArgumentError => e + # puts e # prints "The bucket_name parameter is + # # required" + # end + # + # * Each method can take an +:async+ to turn it into an + # asynchronous operation. Instead of blocking on the response + # to the service call, the method will return a handle on the + # response. For example: + # + # response = client.list_buckets(:async => true) + # response.on_success { p response.buckets.map(&:name) } + # + # @private + class Client < BaseClient + + API_VERSION = '2006-03-01' + + XMLNS = "http://s3.amazonaws.com/doc/#{API_VERSION}/" + + include DataOptions + + configure_client + + protected + def self.bucket_method(method_name, verb, *args, &block) + method_options = (args.pop if args.last.kind_of?(Hash)) || {} + xml_grammar = (args.pop if args.last.respond_to?(:parse)) + verb = verb.to_s.upcase + subresource = args.first + + add_client_request_method(method_name, :xml_grammar => xml_grammar) do + + configure_request do |req, options| + require_bucket_name!(options[:bucket_name]) + req.http_method = verb + req.bucket = options[:bucket_name] + req.add_param(subresource) if subresource + + if header_options = method_options[:header_options] + header_options.each do |(option_name, header)| + req.headers[header] = options[option_name] if + options[option_name] + end + end + + end + + instance_eval(&block) if block + end + end + + protected + def self.object_method(method_name, verb, *args, &block) + bucket_method(method_name, verb, *args) do + configure_request do |req, options| + validate_key!(options[:key]) + super(req, options) + req.key = options[:key] + end + + instance_eval(&block) if block + end + end + + public + + bucket_method(:create_bucket, :put) do + configure_request do |req, options| + validate_bucket_name!(options[:bucket_name]) + req.canned_acl = options[:acl] + if location = options[:location_constraint] + xmlns = "http://s3.amazonaws.com/doc/#{API_VERSION}/" + req.body = <<-XML + + #{location} + + XML + end + super(req, options) + end + end + + ## + # Deletes a bucket. + # + # == Required Options + # + # * +:bucket_name+ -- The name of the bucket. + bucket_method(:delete_bucket, :delete) + + ## + # Lists the buckets in the account. + add_client_request_method(:list_buckets) do + configure_request do |req, options| + req.http_method = "GET" + end + + process_response do |resp| + XML::ListBuckets.parse(resp.http_response.body, :context => resp) + end + end + + ## + # Sets the access policy for a bucket. + # + # == Required Options + # + # * +:bucket_name+ -- The name of the bucket. + # + # * +:policy+ -- The new policy. This can be a string (which + # is assumed to contain a valid policy expressed in JSON), a + # Policy or any object that responds to +to_json+. + # + # == Response + # + # The response contains only the standard fields. + bucket_method(:set_bucket_policy, :put, 'policy') do + + configure_request do |req, options| + require_policy!(options[:policy]) + super(req, options) + policy = options[:policy] + policy = policy.to_json unless policy.respond_to?(:to_str) + req.body = policy + end + + end + + ## + # Gets the access policy for a bucket. + # + # == Required Options + # + # * +:bucket_name+ -- The name of the bucket. + # + # == Response + # + # A successful response will have a +policy+ method that + # returns an instance of Policy. + # + bucket_method(:get_bucket_policy, :get, 'policy') do + process_response do |resp| + # FIXME: this makes unit testing easier, but is there something + # we should be doing in case of invalid JSON from the service? + policy = Policy.from_json(resp.http_response.body) rescue nil + MetaUtils.extend_method(resp, :policy) { policy } + end + end + + ## + # Deletes the access policy for a bucket. + # + # == Required Options + # + # * +:bucket_name+ -- The name of the bucket. + # + bucket_method(:delete_bucket_policy, :delete, 'policy') + + bucket_method(:set_bucket_versioning, :put, 'versioning') do + configure_request do |req, options| + state = options[:state].to_s.downcase.capitalize + unless state =~ /^(Enabled|Suspended)$/ + raise ArgumentError, "invalid versioning state `#{state}`" + end + super(req, options) + req.body = <<-XML.strip + + #{state} + + XML + end + end + + ## + # Gets the bucket's location constraint. + # @return [String] The bucket location constraint. Returns nil if + # the bucket was created in the US classic region. + bucket_method(:get_bucket_location, :get, 'location') do + process_response do |response| + regex = />(.*)<\/LocationConstraint>/ + matches = response.http_response.body.match(regex) + location = matches ? matches[1] : nil + MetaUtils.extend_method(response, :location_constraint) { location } + end + end + + bucket_method(:get_bucket_versioning, :get, 'versioning', + XML::GetBucketVersioning) + + bucket_method(:list_object_versions, :get, 'versions', + XML::ListObjectVersions) do + + configure_request do |req, options| + super(req, options) + params = %w(delimiter key_marker max_keys prefix version_id_marker) + params.each do |param| + if options[param.to_sym] + req.add_param(param.gsub(/_/, '-'), options[param.to_sym]) + end + end + end + + end + + ## + # Sets the access control list for a bucket. + # + # == Required Options + # + # * +:bucket_name+ -- The name of the bucket. + # + # * +:acl+ -- The new acl. This can be any of the following: + # * An XML policy as a string (which is passed to S3 uninterpreted) + # * An AccessControlList object + # * Any object that responds to +to_xml+ + # * Any Hash that is acceptable as an argument to + # AccessControlList#initialize. + # + # == Response + # + # The response contains only the standard fields. + bucket_method(:set_bucket_acl, :put, 'acl') do + configure_request do |req, options| + require_acl!(options[:acl]) + super(req, options) + if options[:acl].kind_of?(Hash) + req.body = AccessControlList.new(options[:acl]).to_xml + elsif options[:acl].respond_to?(:to_str) + req.body = options[:acl] + else + req.body = options[:acl].to_xml + end + end + end + + ## + # Gets the access control list for a bucket. + # + # == Required Options + # + # * +:bucket_name+ -- The name of the bucket. + # + # == Response + # + # A successful response will have an +acl+ method that + # returns an instance of AccessControlList. + # + bucket_method(:get_bucket_acl, :get, 'acl', + XML::GetBucketAcl) + + ## + # Sets the access control list for an object. + # + # == Required Options + # + # * +:bucket_name+ -- The name of the bucket. + # + # * +:key+ -- The key of the object. + # + # * +:acl+ -- The new acl. This can be a string (which is + # assumed to contain a valid ACL expressed in XML), a + # AccessControlList or any object whose +to_xml+ returns a + # valid ACL expressed in XML. + # + # == Response + # + # The response contains only the standard fields. + object_method(:set_object_acl, :put, 'acl') do + configure_request do |req, options| + require_acl!(options[:acl]) + super(req, options) + if options[:acl].kind_of?(Hash) + req.body = AccessControlList.new(options[:acl]).to_xml + elsif options[:acl].respond_to?(:to_str) + req.body = options[:acl] + else + req.body = options[:acl].to_xml + end + end + end + + ## + # Gets the access control list for an object. + # + # == Required Options + # + # * +:bucket_name+ -- The name of the bucket. + # + # * +:key+ -- The key of the object. + # + # == Response + # + # A successful response will have an +acl+ method that + # returns an instance of AccessControlList. + object_method(:get_object_acl, :get, 'acl', + XML::GetBucketAcl) + + ## + # Puts data into an object, replacing the current contents. + # + # == Required Options + # + # * +:bucket_name+ -- The name of the bucket that will contain the data. + # + # * +:key+ -- The key under which the data will be saved. + # + # * +:data+ -- The data to upload. This can be provided as an option + # or when using block form (see below). Valid values include: + # + # * A string + # + # * A Pathname object. + # + # * Any object with +read+ and +eof?+ methods that behave + # like Ruby's IO class (e.g. IO, File, Tempfile, StringIO, etc). + # The object must support the following access methods: + # + # read # all at once + # read(length) until eof? # in chunks + # + # == Optional + # + # * +:content_length+ -- Required if you are using block form to + # write data or if it is not possible to determine the size of + # +:data+. Best effort is made to determine the content length of + # strings, files, tempfiles, io objects, and any object that responds + # to #length or #size. + # + # * +:metadata+ -- A hash of metadata to be included with the + # object. These will be sent to S3 as headers prefixed with + # +x-amz-meta+. + # + # * +:acl+ -- A canned access control policy, valid values are: + # * +:private+ + # * +:public_read+ + # * ... + # Defaults to +:private+ + # + # * +:storage_class+ -- Controls whether Reduced Redundancy + # Storage is enabled for the object. Valid values are + # +:standard+ (the default) or +:reduced_redundancy+ + # + # * +:cache_control+ -- Can be used to specify caching + # behavior [...] + # + # * +:content_disposition+ -- Specifies presentational + # information [...] + # + # * +:content_encoding+ -- Specifies what content encodings + # have been [...] + # + # * +:content_md5+ -- The base64 encoded 128-bit [...] + # + # * +:content_type+ -- A standard MIME type describing [...] + # + # == Block Form + # + # In block form, this method yields a stream to the block that + # accepts data chunks. For example: + # + # s3_client.put_object( + # :bucket_name => 'mybucket', + # :key => 'some/key' + # :content_length => File.size('myfile') + # ) do |buffer| + # + # File.open('myfile') do |io| + # buffer.write(io.read(length)) until io.eof? + # end + # + # end + # + # This form is useful if you need finer control over how + # potentially large amounts of data are read from another + # source before being sent to S3; for example, if you are + # using a non-blocking IO model and reading from a large file + # on disk or from another network stream. Some HTTP handlers + # do not support streaming request bodies, so if you plan to + # upload large objects using this interface you should make + # sure the HTTP handler you configure for the client meets + # your needs. + # + # == Response + # + # If bucket versioning is enabled, a successful response will + # have a +version_id+ method that returns the version ID of + # the version that was written in the request. + # + object_method(:put_object, :put, + :header_options => { + :content_md5 => 'Content-MD5', + :cache_control => 'Cache-Control', + :content_disposition => 'Content-Disposition', + :content_encoding => 'Content-Encoding', + :content_type => 'Content-Type', + :storage_class => 'x-amz-storage-class' + }) do + configure_request do |request, options, block| + super(request, options) + set_request_data(request, options, block) + request.metadata = options[:metadata] + request.canned_acl = options[:acl] + request.storage_class = options[:storage_class] + end + + process_response do |response| + MetaUtils.extend_method(response, :version_id) do + response.http_response.header('x-amz-version-id') + end + MetaUtils.extend_method(response, :etag) do + response.http_response.header('ETag') + end + end + + simulate_response do |response| + MetaUtils.extend_method(response, :etag) { "abc123" } + MetaUtils.extend_method(response, :version_id) { nil } + end + end + + ## + # Gets the data for a key. + # + # == Required Options + # + # * +:bucket_name+ -- The name of the bucket that contains the data. + # + # * +:key+ -- The key under which the data exists. + # + # == Optional + # + # * +:if_modified_since+ -- A Time object; if specified, the + # response will contain an additional +modified?+ method that + # returns true if the object was modified after the given + # time. If +modified?+ returns false, the +data+ method of + # the response will return +nil+. + # + # * +:if_unmodified_since+ -- A Time object; if specified, the + # response will contain an additional +unmodified?+ method + # that returns true if the object was not modified after the + # given time. If +unmodified?+ returns false, the +data+ + # method of the response will return +nil+. + # + # * +:if_match+ -- A string; if specified, the response will + # contain an additional +matches?+ method that returns true + # if the object ETag matches the value for this option. If + # +matches?+ returns false, the +data+ method of the + # response will return +nil+. + # + # * +:if_none_match+ -- A string; if specified, the response + # will contain an additional +matches?+ method that returns + # true if and only if the object ETag matches the value for + # this option. If +matches?+ returns true, the +data+ + # method of the response will return +nil+. + # + # * +:to+ -- A destination for the data. Valid values: + # + # * The path to a file as a string + # + # * A Pathname object + # + # * Any object that supports write(data) and + # +close+ methods like Ruby's IO class + # + # * +:range+ -- TODO: figure out the format for this + # parameter. + # + # == Response + # + # A successful response will have some combination of the + # following methods: + # + # * +data+ -- The object data as a string. This will return + # +nil+ if one of the conditional options above is specified + # and the condition is not met. It will also return +nil+ + # if +deleted?+ returns true. It will not be present if the + # +:to+ option is specified. + # + # * +modified?+, +unmodified?+, +matches?+ -- These will be + # present as documented in the conditional options above. + # + # * +version_id+ -- Returns the version ID of the object that + # was written (only for versioned buckets). + # + # * +deleted?+ -- This will return +true+ if the bucket has + # versioning enabled and the object retrieved was a delete + # marker. + object_method(:get_object, :get, + :header_options => { + :range => "Range", + :if_modified_since => "If-Modified-Since", + :if_unmodified_since => "If-Unmodified-Since", + :if_match => "If-Match", + :if_none_match => "If-None-Match" + }) do + configure_request do |req, options| + super(req, options) + if options[:version_id] + req.add_param('versionId', options[:version_id]) + end + ["If-Modified-Since", + "If-Unmodified-Since"].each do |date_header| + case value = req.headers[date_header] + when DateTime + req.headers[date_header] = Time.parse(value.to_s).rfc2822 + when Time + req.headers[date_header] = value.rfc2822 + end + end + end + + process_response do |resp| + MetaUtils.extend_method(resp, :data) { resp.http_response.body } + MetaUtils.extend_method(resp, :version_id) do + http_response.header('x-amz-version-id') + end + end + end + + object_method(:head_object, :head) do + configure_request do |req, options| + super(req, options) + if options[:version_id] + req.add_param('versionId', options[:version_id]) + end + end + + process_response do |resp| + + # create a hash of user-supplied metadata + MetaUtils.extend_method(resp, :meta) do + meta = {} + resp.http_response.headers.each_pair do |name,value| + if name =~ /^x-amz-meta-(.+)$/i + meta[$1] = [value].flatten.join + end + end + meta + end + + # create methods for standard response headers + { + 'x-amz-version-id' => :version_id, + 'content-type' => :content_type, + 'etag' => :etag, + }.each_pair do |header,method| + MetaUtils.extend_method(resp, method) do + http_response.header(header) + end + end + + MetaUtils.extend_method(resp, :content_length) do + http_response.header('content-length').to_i + end + end + end + + object_method(:delete_object, :delete) do + configure_request do |req, options| + super(req, options) + if options[:version_id] + req.add_param('versionId', options[:version_id]) + end + end + + process_response do |resp| + MetaUtils.extend_method(resp, :version_id) do + http_response.header('x-amz-version-id') + end + end + + end + + bucket_method(:list_objects, :get, XML::ListObjects) do + configure_request do |req, options| + super(req, options) + params = %w(delimiter marker max_keys prefix) + params.each do |param| + if options[param.to_sym] + req.add_param(param.gsub(/_/, '-'), options[param.to_sym]) + end + end + end + end + + object_method(:initiate_multipart_upload, :post, 'uploads', + XML::InitiateMultipartUpload, + :header_options => { + :cache_control => 'Cache-Control', + :content_disposition => 'Content-Disposition', + :content_encoding => 'Content-Encoding', + :content_type => 'Content-Type', + :storage_class => 'x-amz-storage-class' + }) do + configure_request do |req, options| + super(req, options) + req.metadata = options[:metadata] + req.canned_acl = options[:acl] + req.storage_class = options[:storage_class] + end + end + + bucket_method(:list_multipart_uploads, + :get, 'uploads', + XML::ListMultipartUploads) do + configure_request do |req, options| + super(req, options) + params = %w(delimiter key_marker max_keys) + + %w(upload_id_marker max_uploads prefix) + params.each do |param| + if options[param.to_sym] + req.add_param(param.gsub(/_/, '-'), options[param.to_sym]) + end + end + end + end + + object_method(:upload_part, :put, + :header_options => { + :content_md5 => 'Content-MD5' + }) do + configure_request do |request, options, block| + require_upload_id!(options[:upload_id]) + validate!("part_number", options[:part_number]) do + "must not be blank" if options[:part_number].to_s.empty? + end + super(request, options) + set_request_data(request, options, block) + request.add_param('uploadId', options[:upload_id]) + request.add_param('partNumber', options[:part_number]) + end + + process_response do |response| + MetaUtils.extend_method(response, :etag) do + response.http_response.header('ETag') + end + end + + simulate_response do |response| + MetaUtils.extend_method(response, :etag) { "abc123" } + end + end + + object_method(:complete_multipart_upload, :post, + XML::CompleteMultipartUpload) do + configure_request do |req, options| + require_upload_id!(options[:upload_id]) + validate_parts!(options[:parts]) + super(req, options) + req.add_param('uploadId', options[:upload_id]) + parts_xml = options[:parts].map do |part| + ""+ + "#{part[:part_number].to_i}"+ + "#{REXML::Text.normalize(part[:etag].to_s)}"+ + "" + end.join + req.body = + "#{parts_xml}" + end + + process_response do |response| + MetaUtils.extend_method(response, :version_id) do + response.http_response.header('x-amz-version-id') + end + end + + simulate_response do |response| + MetaUtils.extend_method(response, :version_id) { nil } + end + end + + object_method(:abort_multipart_upload, :delete) do + configure_request do |req, options| + require_upload_id!(options[:upload_id]) + super(req, options) + req.add_param('uploadId', options[:upload_id]) + end + end + + object_method(:list_parts, :get, + XML::ListParts) do + configure_request do |req, options| + require_upload_id!(options[:upload_id]) + super(req, options) + req.add_param('uploadId', options[:upload_id]) + req.add_param('max-parts', options[:max_parts]) + req.add_param('part-number-marker', options[:part_number_marker]) + end + end + + ## + # @param [Hash] options + # @option options [required, String] :bucket_name Name of the bucket + # to copy a object into. + # @option options [required, String] :key Where (object key) in the + # bucket the object should be copied to. + # @option options [required, String] :copy_source The name of the + # source bucket and key name of the source object, separated by a + # slash (/). This string must be URL-encoded. Additionally, the + # source bucket must be valid and you must have READ access to + # the valid source object. + # @option options [Symbol] :acl + # + object_method(:copy_object, :put, + :header_options => { + :copy_source => 'x-amz-copy-source', + :metadata_directive => 'x-amz-metadata-directive', + :storage_class => 'x-amz-storage-class', + } + ) do + + configure_request do |req, options| + # TODO : validate presence of copy source + # TODO : validate metadata directive COPY / REPLACE + # TODO : validate storage class STANDARD / REDUCED_REDUNDANCY + # TODO : add validations for storage class in other places used + super(req, options) + req.canned_acl = options[:acl] + req.metadata = options[:metadata] + req.storage_class = options[:storage_class] + if options[:version_id] + req.headers['x-amz-copy-source'] += "?versionId=#{options[:version_id]}" + end + end + + process_response do |response| + MetaUtils.extend_method(response, :version_id) do + response.http_response.header('x-amz-version-id') + end + MetaUtils.extend_method(response, :etag) do + response.http_response.header('ETag') + end + end + + end + + protected + def xml_error_response? response + (response.http_response.status >= 300 || + response.request_type == :complete_multipart_upload) and + XmlGrammar.parse(response.http_response.body).respond_to?(:code) + end + + protected + def should_retry? response + super or + response.request_type == :complete_multipart_upload && + xml_error_response?(response) + end + + protected + def set_request_data request, options, block + request.body_stream = data_stream_from(options, &block) + request.headers['Content-Length'] = content_length_from(options) + end + + protected + def new_request + S3::Request.new + end + + module Validators + + # Returns true if the given bucket name is valid. + def valid_bucket_name?(bucket_name) + validate_bucket_name!(bucket_name) rescue false + end + + def dns_compatible_bucket_name?(bucket_name) + return false if + !valid_bucket_name?(bucket_name) or + + # Bucket names should not contain underscores (_) + bucket_name["_"] or + + # Bucket names should be between 3 and 63 characters long + bucket_name.size > 63 or + + # Bucket names should not end with a dash + bucket_name[-1,1] == '-' or + + # Bucket names cannot contain two, adjacent periods + bucket_name['..'] or + + # Bucket names cannot contain dashes next to periods + # (e.g., "my-.bucket.com" and "my.-bucket" are invalid) + (bucket_name['-.'] || bucket_name['.-']) + + true + end + + protected + def validate! name, value, &block + if error_msg = yield + raise ArgumentError, "#{name} #{error_msg}" + end + value + end + + protected + def validate_key!(key) + validate!('key', key) do + case + when key.nil? || key == '' + 'may not be blank' + end + end + end + + protected + def require_bucket_name! bucket_name + if [nil, ''].include?(bucket_name) + raise ArgumentError, "bucket_name may not be blank" + end + end + + + # Returns true if the given bucket name is valid. If the name + # is invalid, an ArgumentError is raised. + protected + def validate_bucket_name!(bucket_name) + validate!('bucket_name', bucket_name) do + case + when bucket_name.nil? || bucket_name == '' + 'may not be blank' + when bucket_name !~ /^[a-z0-9._\-]+$/ + 'may only contain lowercase letters, numbers, periods (.), ' + + 'underscores (_), and dashes (-)' + when bucket_name !~ /^[a-z0-9]/ + 'must start with a letter or a number' + when !(3..255).include?(bucket_name.size) + 'must be between 3 and 255 characters long' + when bucket_name =~ /(\d+\.){3}\d+/ + 'must not be formatted like an IP address (e.g., 192.168.5.4)' + when bucket_name =~ /\n/ + 'must not contain a newline character' + end + end + end + + protected + def require_policy!(policy) + validate!('policy', policy) do + case + when policy.nil? || policy == '' + 'may not be blank' + else + json_validation_message(policy) + end + end + end + + protected + def require_acl!(acl) + validate!('acl', acl) do + case + when acl.kind_of?(Hash) + AccessControlList.new(acl).validate! + nil + when !acl.respond_to?(:to_str) && !acl.respond_to?(:to_xml) + "must support to_xml: #{acl.inspect}" + when acl.nil? || acl == '' + 'may not be blank' + else + xml_validation_message(acl) + end + end + end + + protected + def require_upload_id!(upload_id) + validate!("upload_id", upload_id) do + "must not be blank" if upload_id.to_s.empty? + end + end + + protected + def validate_parts!(parts) + validate!("parts", parts) do + if !parts.kind_of?(Array) + "must not be blank" + elsif parts.empty? + "must contain at least one entry" + elsif !parts.all? { |p| p.kind_of?(Hash) } + "must be an array of hashes" + elsif !parts.all? { |p| p[:part_number] } + "must contain part_number for each part" + elsif !parts.all? { |p| p[:etag] } + "must contain etag for each part" + elsif parts.any? { |p| p[:part_number].to_i < 1 } + "must not have part numbers less than 1" + end + end + end + + protected + def json_validation_message(obj) + if obj.respond_to?(:to_str) + obj = obj.to_str + elsif obj.respond_to?(:to_json) + obj = obj.to_json + end + + error = nil + begin + JSON.parse(obj) + rescue => e + error = e + end + "contains invalid JSON: #{error}" if error + end + + protected + def xml_validation_message(obj) + if obj.respond_to?(:to_str) + obj = obj.to_str + elsif obj.respond_to?(:to_xml) + obj = obj.to_xml + end + + error = nil + begin + REXML::Document.new(obj) + rescue => e + error = e + end + "contains invalid XML: #{error}" if error + end + + end + + include Validators + extend Validators + + end + + end +end diff --git a/lib/aws/s3/client/xml.rb b/lib/aws/s3/client/xml.rb new file mode 100644 index 00000000000..955a66cf314 --- /dev/null +++ b/lib/aws/s3/client/xml.rb @@ -0,0 +1,190 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/xml_grammar' +require 'aws/base_client' + +module AWS + class S3 + class Client < BaseClient + + # @private + module XML + + Error = XmlGrammar.customize { } + + module HasCommonPrefixes + + def self.included(mod) + mod.module_eval do + element "CommonPrefixes" do + collect_values + format_value {|value| value.prefix } + end + end + end + + end + + ListBuckets = XmlGrammar.customize do + element "Buckets" do + element "Bucket" do + collect_values + end + format_value { |value| super(value.bucket) } + end + end + + GetBucketAcl = GetObjectAcl = XmlGrammar.customize do + wrapper(:acl, + :for => ["Owner", + "AccessControlList"]) do + construct_value { AccessControlList.new } + end + + element "Owner" do + construct_value { AccessControlList::Owner.new } + end + + element "AccessControlList" do + element "Grant" do + collect_values + construct_value { AccessControlList::Grant.new } + + element "Grantee" do + construct_value { AccessControlList::Grantee.new } + element "ID" do + rename :canonical_user_id + end + end + + element "Permission" do + symbol_value + end + end + + format_value { |value| super(value.grant) } + rename :grants + + end + end + + ListObjects = XmlGrammar.customize do + + element("Name") { rename "bucket_name" } + element("MaxKeys") { integer_value } + element("IsTruncated") { rename "truncated"; boolean_value } + element("Delimiter") { force } + + element("Contents") do + list + element("Owner") do + element("ID") { } + element("DisplayName") { } + end + element("Key") { } + element("Size") { } + element("StorageClass") { } + element("ETag") { rename "etag" } + + # DateTime is more general, Time is much faster to construct + element("LastModified") { time_value } + end + + include HasCommonPrefixes + + end + + GetBucketVersioning = XmlGrammar.customize do + element("Status") do + symbol_value + format_value {|value| super(value) || :unversioned } + force + end + end + + ListObjectVersions = XmlGrammar.customize do + + element("MaxKeys") { integer_value } + element("IsTruncated") { rename "Truncated"; boolean_value } + element("NextKeyMarker") { force } + element("NextVersionIdMarker") { force } + + %w(DeleteMarker Version).each do |element_name| + element(element_name) do + collect_values + rename("versions") + element("IsLatest") { rename "latest"; boolean_value } + element("LastModified") { datetime_value } + element("ETag") { rename "etag" } + element("Size") { integer_value } + element("StorageClass") { symbol_value } + end + end + + element "DeleteMarker" do + add_method(:delete_marker?) { true } + add_method(:version?) { false } + end + + element "Version" do + add_method(:delete_marker?) { false } + add_method(:version?) { true } + end + + include HasCommonPrefixes + + end + + # default behavior is good enough + InitiateMultipartUpload = XmlGrammar.customize do + element("UploadId") { force } + end + + ListMultipartUploads = XmlGrammar.customize do + element("IsTruncated") { rename "Truncated"; boolean_value } + element("MaxUploads") { integer_value } + element("NextKeyMarker") { force } + element("NextUploadIdMarker") { force } + element("Upload") do + collect_values + rename :uploads + element("StorageClass") { symbol_value } + element("Initiated") { datetime_value } + end + + include HasCommonPrefixes + end + + # keep default behavior + CompleteMultipartUpload = XmlGrammar.customize + + ListParts = XmlGrammar.customize do + element("StorageClass") { symbol_value } + element("IsTruncated") { rename "Truncated"; boolean_value } + element("MaxParts") { integer_value } + element("PartNumberMarker") { integer_value } + element("NextPartNumberMarker") { integer_value } + element("Part") do + collect_values + rename :parts + element("PartNumber") { integer_value } + element("LastModified") { datetime_value } + element("Size") { integer_value } + end + end + + end + end + end +end diff --git a/lib/aws/s3/data_options.rb b/lib/aws/s3/data_options.rb new file mode 100644 index 00000000000..2fc29c3ca11 --- /dev/null +++ b/lib/aws/s3/data_options.rb @@ -0,0 +1,99 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'pathname' + +module AWS + class S3 + module DataOptions + + protected + def data_stream_from options, &block + + validate_data!(options, block) + + # block format + if block_given? + buffer = StringIO.new + yield(buffer) + buffer.rewind + return buffer + end + + # string, pathname, file, io-like object, etc + data = options[:data] + file_opts = ["r"] + file_opts << { :encoding => "BINARY" } if Object.const_defined?(:Encoding) + case + when data.is_a?(String) + data.force_encoding("BINARY") if data.respond_to?(:force_encoding) + StringIO.new(data) + when data.is_a?(Pathname) then File.open(data.to_s, *file_opts) + when options[:file] then File.open(options[:file], *file_opts) + else data + end + + end + + protected + def content_length_from options + data = options[:data] + case + when options[:content_length] then options[:content_length] + when options[:file] then File.size(options[:file]) + when data.is_a?(Pathname) then File.size(data.to_s) + when data.is_a?(File) then File.size(data.path) + when data.respond_to?(:bytesize) then data.bytesize + when data.respond_to?(:size) then data.size + when data.respond_to?(:length) then data.length + else raise ArgumentError, 'content_length was not provided ' + + 'and could not be determined' + end + end + + protected + def validate_data! options, block + + data = options[:data] + filename = options[:file] + + raise ArgumentError, 'data passed multiple ways' if + [data, filename, block].compact.size > 1 + + # accepting block format + return if block and block.arity == 1 + + # accepting file path + return if filename.kind_of?(String) + + # accepting strings + return if data.kind_of?(String) + + # accepting pathname + return if data.kind_of?(Pathname) + + # accepts io-like objects (responds to read and eof?) + if data.respond_to?(:read) and + data.method(:read).arity != 0 and + data.respond_to?(:eof?) then + return true + end + + raise ArgumentError, 'data must be provided as a String, ' + + 'Pathname, file path, or an object that responds to #read and #eof?' + + end + + end + end +end diff --git a/lib/aws/s3/errors.rb b/lib/aws/s3/errors.rb new file mode 100644 index 00000000000..376e2433502 --- /dev/null +++ b/lib/aws/s3/errors.rb @@ -0,0 +1,43 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/lazy_error_classes' +require 'aws/s3/client/xml' + +module AWS + class S3 + + # This module contains exception classes for each of the error + # types that S3 can return. You can use these classes to rescue + # specific errors, for example: + # + # begin + # S3.new.buckets.mybucket. + # objects.myobj.write("HELLO") + # rescue S3::Errors::NoSuchBucket => e + # S3.new.buckets.create("mybucket") + # retry + # end + # + # All errors raised as a result of error responses from the + # service are instances of either {ClientError} or {ServerError}. + # @private + module Errors + + BASE_ERROR_GRAMMAR = Client::XML::Error + + include LazyErrorClasses + + end + end +end diff --git a/lib/aws/s3/multipart_upload.rb b/lib/aws/s3/multipart_upload.rb new file mode 100644 index 00000000000..fe3581b3113 --- /dev/null +++ b/lib/aws/s3/multipart_upload.rb @@ -0,0 +1,318 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/uploaded_part' +require 'aws/s3/uploaded_part_collection' +require 'thread' + +module AWS + class S3 + + # Represents a multipart upload to an S3 object. See + # {S3Object#multipart_upload} for a convenient way to initiate a + # multipart upload. + class MultipartUpload + + include Model + + # @private + def initialize(object, id, options = {}) + @id = id + @object = object + + super + + @completed_parts = {} + @increment_mutex = Mutex.new + @completed_mutex = Mutex.new + @last_part = 0 + end + + def bucket + object.bucket + end + + def inspect + "<#{self.class}:#{object.bucket.name}/#{object.key}:#{id}>" + end + # @return [String] Returns the upload id. + attr_reader :id + + alias_method :upload_id, :id + + # @return [S3Object] Returns the object this upload is intended for. + attr_reader :object + + # @return [Boolean] Returns true if both multipart uploads + # represent the same object and upload. + def ==(other) + other.kind_of?(MultipartUpload) and + other.object == object and + other.id == id + end + + alias_method :eql?, :== + + # @return [Boolean] True if the upload exists. + def exists? + client.list_parts(base_opts) + rescue Errors::NoSuchUpload => e + false + else + true + end + + # @return The upload initiator. This object will have +:id+ + # and +:display_name+ methods; if the initiator is an IAM + # user, the +:id+ method will return the ARN of the user, and + # if the initiator is an AWS account, this method will return + # the same data as {#owner}. + def initiator + client.list_parts(base_opts).initiator + end + + # @return The upload owner. This object will have +:id+ + # and +:display_name+ methods. + def owner + client.list_parts(base_opts).owner + end + + # @return [Symbol] The class of storage used to store the + # uploaded object. Possible values: + # + # * +:standard+ + # * +:reduced_redundancy?+ + def storage_class + client.list_parts(base_opts).storage_class.downcase.to_sym + end + + # @return [Boolean] True if the uploaded object will be stored + # with reduced redundancy. + def reduced_redundancy? + storage_class == :reduced_redundancy + end + + # Aborts the upload. After a multipart upload is aborted, no + # additional parts can be uploaded using that upload ID. The + # storage consumed by any previously uploaded parts will be + # freed. However, if any part uploads are currently in + # progress, those part uploads might or might not succeed. As + # a result, it might be necessary to abort a given multipart + # upload multiple times in order to completely free all + # storage consumed by all parts. + # @return [nil] + def abort + client.abort_multipart_upload(base_opts) + @aborted = true + nil + end + alias_method :delete, :abort + alias_method :cancel, :abort + + # @return [Boolean] True if the upload has been aborted. + # @see #abort + def aborted? + @aborted + end + + # Uploads a part. + # + # @overload add_part(data, options = {}) + # + # @param data The data to upload. Valid values include: + # + # * A string + # + # * A Pathname object + # + # * Any object responding to +read+ and +eof?+; the object + # must support the following access methods: + # + # read # all at once + # read(length) until eof? # in chunks + # + # If you specify data this way, you must also include + # the +:content_length+ option. + # + # @param [Hash] options Additional options for the upload. + # + # @option options [Integer] :content_length If provided, + # this option must match the total number of bytes written + # to S3 during the operation. This option is required if + # +:data+ is an IO-like object without a +size+ method. + # + # @overload add_part(options) + # + # @param [Hash] options Options for the upload. Either + # +:data+ or +:file+ is required. + # + # @option options :data The data to upload. Valid values + # include: + # + # * A string + # + # * A Pathname object + # + # * Any object responding to +read+ and +eof?+; the object + # must support the following access methods: + # + # read # all at once + # read(length) until eof? # in chunks + # + # If you specify data this way, you must also include + # the +:content_length+ option. + # + # @option options [String] :file Can be specified instead of + # +:data+; its value specifies the path of a file to + # upload. + # + # @option options [Integer] :content_length If provided, + # this option must match the total number of bytes written + # to S3 during the operation. This option is required if + # +:data+ is an IO-like object without a +size+ method. + def add_part(data_or_options, options = {}) + if data_or_options.kind_of?(Hash) + part_options = base_opts.merge(data_or_options) + else + part_options = base_opts.merge(:data => data_or_options) + end + part_options.merge!(options) + + unless part_options[:part_number] + @increment_mutex.synchronize do + part_options[:part_number] = (@last_part += 1) + end + end + part_number = part_options[:part_number] + + resp = client.upload_part(part_options) + @completed_mutex.synchronize do + @completed_parts[part_number] = { + :part_number => part_number, + :etag => resp.etag + } + end + UploadedPart.new(self, part_number) + end + + # Completes the upload by assembling previously uploaded + # parts. + # + # @return [S3Object, ObjectVersion] If the bucket has versioning + # enabled, returns the {ObjectVersion} representing the + # version that was uploaded. If versioning is disabled, + # returns the object. + def complete(*parts) + parts = parts.flatten + case parts.first + when :remote_parts + complete_opts = get_complete_opts + when :local_parts, nil + complete_opts = base_opts.merge(:parts => completed_parts) + else + part_numbers = parts.map do |part| + case part + when Integer + part + when UploadedPart + raise ArgumentError.new("cannot complete an upload with parts "+ + "from a different upload") unless + part.upload == self + + part.part_number + else + raise ArgumentError.new("expected number or UploadedPart") + end + end + complete_opts = get_complete_opts(part_numbers) + end + + raise "no parts uploaded" if complete_opts[:parts].empty? + + resp = client.complete_multipart_upload(complete_opts) + if resp.version_id + ObjectVersion.new(object, resp.version_id) + else + object + end + end + + # Completes the upload or aborts it if no parts have been + # uploaded yet. Does nothing if the upload has already been + # aborted. + # + # @return [S3Object, ObjectVersion] If the bucket has versioning + # enabled, returns the {ObjectVersion} representing the + # version that was uploaded. If versioning is disabled, + # returns the object. If no upload was attempted (e.g. if it + # was aborted or if no parts were uploaded), returns +nil+. + def close + return if aborted? + if completed_parts.empty? + abort + else + complete + end + end + + # @return [UploadedPartCollection] A collection representing + # the parts that have been uploaded to S3 for this upload. + def parts + UploadedPartCollection.new(self) + end + + # @private + def completed_parts + @completed_parts.values. + sort { |a, b| a[:part_number] <=> b[:part_number] } + end + + # @private + def inspect + "<#{self.class}:#{object.bucket.name}/#{object.key}:#{id}>" + end + + # @private + private + def get_complete_opts(part_numbers = nil) + parts_resp = client.list_parts(base_opts) + complete_opts = + base_opts.merge(:parts => + parts_resp.parts.map do |part| + { :part_number => part.part_number, + :etag => part.etag } + end) + + complete_opts[:parts].reject! do |part| + !part_numbers.include?(part[:part_number]) + end if part_numbers + + complete_opts + end + + # @private + private + def base_opts + opts = { + :bucket_name => object.bucket.name, + :key => object.key + } + opts[:upload_id] = upload_id if upload_id + opts + end + + end + + end +end diff --git a/lib/aws/s3/multipart_upload_collection.rb b/lib/aws/s3/multipart_upload_collection.rb new file mode 100644 index 00000000000..4f8bc552f63 --- /dev/null +++ b/lib/aws/s3/multipart_upload_collection.rb @@ -0,0 +1,78 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/prefix_and_delimiter_collection' +require 'aws/s3/multipart_upload' +require 'aws/s3/s3_object' + +module AWS + class S3 + + # Represents the uploads in progress for a bucket. + # + # @example Finding uploads by prefix + # bucket.multipart_uploads.with_prefix("photos/"). + # map { |upload| upload.object.key } + # # => ["photos/1.jpg", "photos/2.jpg", ...] + # + # @example Browsing with a tree interface + # bucket.multipart_uploads.with_prefix("photos").as_tree. + # children.select(&:branch?).map(&:prefix) + # # => ["photos/2010", "photos/2011", ...] + # + # @see Tree + class MultipartUploadCollection + + include Enumerable + include Model + include PrefixAndDelimiterCollection + + # @return [Bucket] The bucket in which the uploads are taking + # place. + attr_reader :bucket + + # @private + def initialize(bucket, opts = {}) + @bucket = bucket + super + end + + protected + def each_member_in_page(page, &block) + super + page.uploads.each do |u| + object = S3Object.new(bucket, u.key) + upload = MultipartUpload.new(object, u.upload_id) + yield(upload) + end + end + + protected + def list_request(options) + client.list_multipart_uploads(options) + end + + protected + def limit_param; :max_uploads; end + + protected + def pagination_markers; super + [:upload_id_marker]; end + + protected + def page_size(resp); super + resp.uploads.size; end + + end + + end +end diff --git a/lib/aws/s3/object_collection.rb b/lib/aws/s3/object_collection.rb new file mode 100644 index 00000000000..a64cdd5dae4 --- /dev/null +++ b/lib/aws/s3/object_collection.rb @@ -0,0 +1,159 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/s3_object' +require 'aws/s3/prefix_and_delimiter_collection' + +module AWS + class S3 + + # Represents a collection of S3 objects. + # + # == Getting an S3Object by Key + # + # If you know the key of the object you want, you can reference it this way: + # + # # this will not make any requests against S3 + # object = bucket.objects['foo.jpg'] + # object.key #=> 'foo.jpg' + # + # == Finding objects with a Prefix + # + # Given a bucket with the following keys: + # + # photos/sunset.jpg + # photos/sunrise.jpg + # photos/winter.jpg + # videos/comedy.mpg + # videos/dancing.mpg + # + # You can list objects that share a prefix: + # + # bucket.objects.with_prefix('videos').collect(&:key) + # #=> ['videos/comedy.mpg', 'videos/dancing.mpg'] + # + # == Exploring Objects with a Tree Interface + # + # Given a bucket with the following keys: + # + # README.txt + # videos/wedding.mpg + # videos/family_reunion.mpg + # photos/2010/house.jpg + # photos/2011/fall/leaves.jpg + # photos/2011/summer/vacation.jpg + # photos/2011/summer/family.jpg + # + # tree = bucket.objects.with_prefix.prefix('photos').as_tree + # + # directories = tree.children.select(&:branch?).collect(&:prefix) + # #=> ['photos/2010', 'photos/2011'] + # + class ObjectCollection + + include Model + include Enumerable + include PrefixAndDelimiterCollection + + # @param [Bucket] The S3 bucket this object collection belongs to. + def initialize(bucket, options = {}) + @bucket = bucket + super + end + + # @return [Bucket] The bucket this collection belongs to. + attr_reader :bucket + + # Writes a new object to S3. + # + # The first param is the key you want to write this object to. + # All other params/options are documented in {S3Object#write}. + # + # @see S3Object#write + # + # @param [String] key Where in S3 to write the object. + # @return [S3Object] + def create key, *args + self[key].write(*args) + end + + # Returns an S3Object given its name. For example: + # + # @example + # + # object = bucket.objects['file.txt'] + # object.class #=> S3Object + # + # @param [String] key The object key. + # @return [S3Object] + def [] key + S3Object.new(bucket, key.to_s) + end + + # (see PrefixedCollection#with_prefix) + def with_prefix prefix, mode = :replace + super(prefix, mode) + end + + # Iterates the collection, yielding instances of S3Object. + # + # Use break or raise an exception to terminate the enumeration. + # + # @param [Hash] options + # @option options [Integer] :limit (nil) The maximum number of + # objects to yield. + # @option options [Integer] :batch_size (1000) The number of objects to + # fetch each request to S3. Maximum is 1000 keys at time. + # @return [nil] + def each options = {}, &block + super + end + + # @private + protected + def each_member_in_page(page, &block) + super + page.contents.each do |content| + yield(S3Object.new(bucket, content.key)) + end + end + + # @private + protected + def list_request(options) + client.list_objects(options) + end + + # @private + protected + def limit_param + :max_keys + end + + # @private + protected + def page_size resp + super + resp.contents.size + end + + # @private + protected + def next_markers page + { :marker => (last = page.contents.last and last.key) } + end + + end + + end +end diff --git a/lib/aws/s3/object_metadata.rb b/lib/aws/s3/object_metadata.rb new file mode 100644 index 00000000000..ac07224380a --- /dev/null +++ b/lib/aws/s3/object_metadata.rb @@ -0,0 +1,67 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' + +module AWS + class S3 + + # Returns an object that represents the metadata for an S3 object. + class ObjectMetadata + + include Model + + # @param [S3Object] + # @param [Hash] options + # @option options [String] :version_id A specific version of the object + # to get metadata for + def initialize(object, options = {}) + @object = object + @version_id = options[:version_id] + super + end + + # @return [S3Object] + attr_reader :object + + # Returns the value for the given name stored in the S3Objects + # metadata: + # + # bucket.objects['myobject'].metadata['purpose'] + # # returns nil if the given metadata key has not been set + # + # @return [String,nil] Returns the metadata for the given name. + def [] name + to_h[name.to_s] + end + + # Proxies the method to {#[]}. + # @return (see #[]) + def method_missing name + self[name] + end + + # @return [Hash] Returns the user-generated metadata stored with + # this S3 Object. + def to_h + options = {} + options[:bucket_name] = object.bucket.name + options[:key] = object.key + options[:version_id] = @version_id if @version_id + client.head_object(options).meta + end + + end + + end +end diff --git a/lib/aws/s3/object_upload_collection.rb b/lib/aws/s3/object_upload_collection.rb new file mode 100644 index 00000000000..2c3f016f5c1 --- /dev/null +++ b/lib/aws/s3/object_upload_collection.rb @@ -0,0 +1,83 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/paginated_collection' +require 'aws/s3/multipart_upload' +require 'aws/s3/multipart_upload_collection' +require 'aws/s3/s3_object' + +module AWS + class S3 + + # Represents uploads in progress for a single object. + # + # @example Cancel all uploads for an object + # object.multipart_uploads.each(&:abort) + # + # @example Get an upload by ID + # object.multipart_uploads[id] + class ObjectUploadCollection + + include Enumerable + include Model + + # @return [S3Object] The object to which the uploads belong. + attr_reader :object + + # @private + def initialize(object, opts = {}) + @all_uploads = + MultipartUploadCollection.new(object.bucket). + with_prefix(object.key) + @object = object + super + end + + # Creates a new multipart upload. It is usually more + # convenient to use {S3Object#multipart_upload}. + def create(options = {}) + options[:storage_class] = :reduced_redundancy if + options.delete(:reduced_redundancy) + initiate_opts = { + :bucket_name => object.bucket.name, + :key => object.key + }.merge(options) + id = client.initiate_multipart_upload(initiate_opts).upload_id + MultipartUpload.new(object, id) + end + + # Iterates the uploads in the collection. + # + # @yieldparam [MultipartUpload] upload An upload in the + # collection. + # @return [nil] + def each(options = {}, &block) + @all_uploads.each(options) do |upload| + yield(upload) if upload.object.key == @object.key + end + nil + end + + # @return [MultipartUpload] An object representing the upload + # with the given ID. + # + # @param [String] id The ID of an upload to get. + def [] id + MultipartUpload.new(object, id) + end + + end + + end +end diff --git a/lib/aws/s3/object_version.rb b/lib/aws/s3/object_version.rb new file mode 100644 index 00000000000..02dbe8fb36d --- /dev/null +++ b/lib/aws/s3/object_version.rb @@ -0,0 +1,141 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/errors' + +module AWS + class S3 + + # Represents a single version of an S3Object. + # + # When you enable versioning on a S3 bucket, writing to an object + # will create an object version instead of replacing the existing + # object. + class ObjectVersion + + include Model + + # @param [S3Object] object The object this is a version of. + # @param [String] version_id The unique id for this version. + # @param [Hash] options + # @option options [Boolean] :delete_marker Is this version a + # delete marker? + def initialize(object, version_id, options = {}) + @object = object + @version_id = version_id + @delete_marker = options[:delete_marker] + super + end + + # @return [S3Object] the object this is a version of. + attr_reader :object + + def bucket + object.bucket + end + + # @return [String] The unique version identifier. + attr_reader :version_id + + # @return (see S3Object#key) + def key + object.key + end + + # @see S3Object#head + # @return (see S3Object#head) + def head + object.head(:version_id => @version_id) + end + + # @see S3Object#etag + # @return (see S3Object#etag) + def etag + head.etag + end + + # @return (see S3Object#content_length) + def content_length + head.content_length + end + + # @note (see S3Object#content_type) + # @see S3Object#content_type + # @return (see S3Object#content_type) + def content_type + head.content_type + end + + # @see S3Object#metadata + # @return (see S3Object#metadata) + def metadata + object.metadata(:version_id => @version_id) + end + + # Reads the data from this object version. + # @see S3Object#read + # @options (see S3Object#read) + # @return (see S3Object#read) + def read options = {}, &block + object.read(options.merge(:version_id => @version_id), &block) + end + + # Deletes this object version from S3. + # @return (see S3Object#delete) + def delete + object.delete(:version_id => @version_id) + end + + # @return [Boolean] Returns this if this is the latest version of + # the object, false if the object has been written to since + # this version was created. + def latest? + object.versions.latest.version_id == self.version_id + end + + # If you delete an object in a versioned bucket, a delete marker + # is created. + # @return [Boolean] Returns true if this version is a delete marker. + def delete_marker? + if @delete_marker.nil? + begin + # S3 responds with a 405 (method not allowed) when you try + # to HEAD an s3 object version that is a delete marker + metadata['foo'] + @delete_marker = false + rescue Errors::MethodNotAllowed => error + @delete_marker = true + end + end + @delete_marker + end + + # @return [Boolean] Returns true if the other object version has + # the same s3 object key and version id. + def ==(other) + other.kind_of?(ObjectVersion) and + other.object == object and + other.version_id == version_id + end + + alias_method :eql?, :== + + # @private + def inspect + "<#{self.class}:#{object.bucket.name}:#{object.key}:#{version_id}>" + end + + end + end +end diff --git a/lib/aws/s3/object_version_collection.rb b/lib/aws/s3/object_version_collection.rb new file mode 100644 index 00000000000..755056dd8d9 --- /dev/null +++ b/lib/aws/s3/object_version_collection.rb @@ -0,0 +1,78 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/object_version' + +module AWS + class S3 + + # For S3 buckets with versioning enabled, objects will store versions + # each time you write to them. + # + # object = bucket.objects['myobj'] + # object.write('1') + # object.write('2') + # object.write('3') + # + # object.versions.collect(&:read) + # #=> ['1', '2', '3'] + # + # If you know the id of a particular version you can get that object. + # + # bucket.objets['myobj'].version[version_id].delete + # + class ObjectVersionCollection + + include Model + include Enumerable + + # @return [S3Object] The object this collection belongs to. + attr_reader :object + + # @param [S3Object] object + def initialize object, options = {} + @object = object + super(options) + end + + # Returns an object that represents a single version of the {#object}. + # @param [String] version_id + # @return [ObjectVersion] + def [] version_id + ObjectVersion.new(object, version_id) + end + + # @note Generally you will just want to grab the object key its key. + # @return [ObjectVersion] Returns the latest version of this object. + def latest + self.find{|version| true } + end + + # Yields once for each version of the {#object}. + # + # @yield [object_version] + # @yieldparam [ObectVersion] object_version + # @return [nil] + def each &block + object.bucket.versions.with_prefix(object.key).each do |version| + if version.key == object.key + yield(version) + end + end + nil + end + + end + end +end diff --git a/lib/aws/s3/paginated_collection.rb b/lib/aws/s3/paginated_collection.rb new file mode 100644 index 00000000000..54f06a60441 --- /dev/null +++ b/lib/aws/s3/paginated_collection.rb @@ -0,0 +1,94 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + class S3 + + # @private + module PaginatedCollection + + def each(options = {}, &block) + each_page(options) do |page| + each_member_in_page(page, &block) + end + nil + end + + protected + def each_member_in_page(page, &block); end + + protected + def each_page(options = {}, &block) + opts = list_options(options) + limit = options[:limit] + batch_size = options[:batch_size] || 1000 + markers = {} + received = 0 + + loop do + page_opts = opts.dup + page_opts.merge!(markers) + page_opts[limit_param] = + limit ? [limit - received, batch_size].min : batch_size + + page = list_request(page_opts) + markers = next_markers(page) + received += page_size(page) + + yield(page) + + return unless page.truncated? + end + end + + protected + def list_request(options) + raise NotImplementedError + end + + protected + def list_options(options) + opts = {} + opts[:bucket_name] = bucket.name if respond_to?(:bucket) + opts + end + + protected + def limit_param + raise NotImplementedError + end + + protected + def pagination_markers + [:key_marker] + end + + protected + def next_markers(page) + pagination_markers.inject({}) do |markers, marker| + markers[marker] = page.send("next_#{marker}") + markers + end + end + + protected + def page_size(resp) + (resp.respond_to?(:common_prefixes) and + prefixes = resp.common_prefixes and + prefixes.size) or 0 + end + + end + + end +end diff --git a/lib/aws/s3/policy.rb b/lib/aws/s3/policy.rb new file mode 100644 index 00000000000..ae465d9c4e8 --- /dev/null +++ b/lib/aws/s3/policy.rb @@ -0,0 +1,76 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/policy' + +module AWS + class S3 + + # @private + class Policy < AWS::Policy + + class Statement < AWS::Policy::Statement + + ACTION_MAPPING = { + :list_buckets => "s3:ListAllMyBuckets", + :create_bucket => "s3:CreateBucket", + :delete_bucket => "s3:DeleteBucket", + :list_objects => "s3:ListBucket", + :list_object_versions => "s3:ListBucketVersions", + :list_multipart_uploads => "s3:ListBucketMultipartUploads", + :get_object => "s3:GetObject", + :get_object_version => "s3:GetObjectVersion", + :put_object => "s3:PutObject", + :get_object_acl => "s3:GetObjectAcl", + :get_object_version_acl => "s3:GetObjectVersionAcl", + :set_object_acl => "s3:PutObjectAcl", + :set_object_acl_version => "s3:PutObjectAclVersion", + :delete_object => "s3:DeleteObject", + :delete_object_version => "s3:DeleteObjectVersion", + :list_multipart_upload_parts => "s3:ListMultipartUploadParts", + :abort_multipart_upload => "s3:AbortMultipartUpload", + :get_bucket_acl => "s3:GetBucketAcl", + :set_bucket_acl => "s3:PutBucketAcl", + :get_bucket_versioning => "s3:GetBucketVersioning", + :set_bucket_versioning => "s3:PutBucketVersioning", + :get_bucket_requester_pays => "s3:GetBucketRequesterPays", + :set_bucket_requester_pays => "s3:PutBucketRequesterPays", + :get_bucket_location => "s3:GetBucketLocation", + :get_bucket_policy => "s3:GetBucketPolicy", + :set_bucket_policy => "s3:PutBucketPolicy", + :get_bucket_notification => "s3:GetBucketNotification", + :set_bucket_notification => "s3:PutBucketNotification" + } + + protected + def resource_arn resource + prefix = 'arn:aws:s3:::' + case resource + when Bucket + "#{prefix}#{resource.name}" + when S3Object + "#{prefix}#{resource.bucket.name}/#{resource.key}" + when ObjectCollection + "#{prefix}#{resource.bucket.name}/#{resource.prefix}*" + when /^arn:/ + resource + else + "arn:aws:s3:::#{resource}" + end + end + + end + + end + end +end diff --git a/lib/aws/s3/prefix_and_delimiter_collection.rb b/lib/aws/s3/prefix_and_delimiter_collection.rb new file mode 100644 index 00000000000..9710deb5727 --- /dev/null +++ b/lib/aws/s3/prefix_and_delimiter_collection.rb @@ -0,0 +1,56 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/s3/prefixed_collection' + +module AWS + class S3 + + # @private + module PrefixAndDelimiterCollection + + include PrefixedCollection + + def each(options = {}, &block) + each_page(options) do |page| + each_member_in_page(page, &block) + end + nil + end + + # @see Bucket#as_tree + def as_tree options = {} + Tree.new(self, { :prefix => prefix }.merge(options)) + end + + # @private + protected + def each_member_in_page(page, &block) + super + page.common_prefixes.each do |p| + yield(with_prefix(p)) + end + end + + # @private + protected + def list_options(options) + opts = super + opts[:delimiter] = options[:delimiter] if options.key?(:delimiter) + opts + end + + end + + end +end diff --git a/lib/aws/s3/prefixed_collection.rb b/lib/aws/s3/prefixed_collection.rb new file mode 100644 index 00000000000..8f0ef520b19 --- /dev/null +++ b/lib/aws/s3/prefixed_collection.rb @@ -0,0 +1,84 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/s3/paginated_collection' + +module AWS + class S3 + + module PrefixedCollection + + include PaginatedCollection + + # @private + def initialize *args + options = args.last.is_a?(Hash) ? args.pop : {} + @prefix = options[:prefix] + args.push(options) + super(*args) + end + + # @return [String,nil] The prefix of this collection. + attr_reader :prefix + + # Returns a new collection with a different prefix + # + # @example + # objects = collection.with_prefix('photos') + # objects.prefix #=> 'photos' + # + # @example Chaining with_prefix replaces previous prefix + # objects = collection.with_prefix('photos').with_prefix('videos') + # objects.prefix #=> 'videos' + # + # @example Chaining with_prefix with :append + # objects = collection.with_prefix('a/').with_prefix('b/', :append) + # objects.prefix #=> 'a/b/' + # + # @example Chaining with_prefix with :prepend + # objects = collection.with_prefix('a/').with_prefix('b/', :prepend) + # objects.prefix #=> 'b/a/' + # + # @param [String] prefix The prefix condition that limits what objects + # are returned by this collection. + # @param [Symbol] mode (:replace) If you chain calls to #with_prefix + # the +mode+ affects if the prefix prepends, appends, or replaces. + # Valid modes are: + # * +:replace+ + # * +:append+ + # * +:prepend+ + # @return [Collection] Returns a new collection with a modified prefix. + def with_prefix prefix, mode = :replace + new_prefix = case mode + when :replace then prefix + when :append then "#{@prefix}#{prefix}" + when :prepend then "#{prefix}#{@prefix}" + else + raise ArgumentError, "invalid prefix mode `#{mode}`, it must be " + + ":replace, :append or :prepend" + end + self.class.new(bucket, + :prefix => new_prefix) + end + + # @private + protected + def list_options(options) + opts = super + opts[:prefix] = prefix if prefix + opts + end + + end + end +end diff --git a/lib/aws/s3/presigned_post.rb b/lib/aws/s3/presigned_post.rb new file mode 100644 index 00000000000..80cd8ac8d3f --- /dev/null +++ b/lib/aws/s3/presigned_post.rb @@ -0,0 +1,504 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/request' +require 'uri' +require 'base64' +require 'time' + +module AWS + class S3 + + # Helper to generate form fields for presigned POST requests to + # a bucket. You can use this to create a form that can be used + # from a web browser to upload objects to S3 while specifying + # conditions on what can be uploaded and how it is processed and + # stored. + # + # @example Form fields for uploading by file name + # form = bucket.presigned_post(:key => "photos/${filename}") + # form.url.to_s # => "https://mybucket.s3.amazonaws.com/" + # form.fields # => { "AWSAccessKeyId" => "...", ... } + # + # @example Generating a minimal HTML form + # form = bucket.objects.myobj.presigned_post + # hidden_inputs = form.fields.map do |(name, value)| + # %() + # end + # <<-END + #
+ # #{hidden_inputs} + # + #
+ # END + # + # @example Restricting the size of the uploaded object + # bucket.presigned_post(:content_length => 1..(10*1024)) + # + # @example Restricting the key prefix + # bucket.presigned_post.where(:key).starts_with("photos/") + class PresignedPost + + include Model + + # @return [Bucket] The bucket to which data can be uploaded + # using the form fields + attr_reader :bucket + + # @return [String] The key of the object that will be + # uploaded. If this is nil, then the object can be uploaded + # with any key that satisfies the conditions specified for + # the upload (see {#where}). + attr_reader :key + + # @return [Hash] A hash of the metadata fields included in the + # signed fields. Additional metadata fields may be provided + # with the upload as long as they satisfy the conditions + # specified for the upload (see {#where}). + attr_reader :metadata + + # @return [Range] The range of acceptable object sizes for the + # upload. By default any size object may be uploaded. + attr_reader :content_length + + # @private + SPECIAL_FIELDS = [:cache_control, + :content_type, + :content_disposition, + :content_encoding, + :expires_header, + :acl, + :success_action_redirect, + :success_action_status] + + # @private + attr_reader :conditions + + # @return [Array] Additional fields which may be sent + # with the upload. These will be included in the policy so + # that they can be sent with any value. S3 will ignore + # them. + attr_reader :ignored_fields + + # @return The expiration time for the signature. By default + # the signature will expire an hour after it is generated. + attr_reader :expires + + # Creates a new presigned post object. + # + # @param [Bucket] bucket The bucket to which data can be uploaded + # using the form fields. + # + # @param [Hash] opts Additional options for the upload. Aside + # from +:secure+, +:expires+, +:content_length+ and +:ignore+ + # the values provided here will be stored in the hash returned + # from the {#fields} method, and the policy in that hash will + # restrict their values to the values provided. If you + # instead want to only restrict the values and not provide + # them -- for example, if your application generates separate + # form fields for those values -- you should use the {#where} + # method on the returned object instead of providing the + # values here. + # + # @option opts [String] :key The key of the object that will + # be uploaded. If this is nil, then the object can be + # uploaded with any key that satisfies the conditions + # specified for the upload (see {#where}). + # + # @option opts [Boolean] :secure By setting this to false, you + # can cause {#url} to return an HTTP URL. By default it + # returns an HTTPS URL. + # + # @option opts [Time, DateTime, Integer, String] :expires The + # time at which the signature will expire. By default the + # signature will expire one hour after it is generated + # (e.g. when {#fields} is called). + # + # When the value is a Time or DateTime, the signature + # expires at the specified time. When it is an integer, the + # signature expires the specified number of seconds after it + # is generated. When it is a string, the string is parsed + # as a time (using Time.parse) and the signature expires at + # that time. + # + # @option opts [String] :cache_control Sets the Cache-Control + # header stored with the object. + # + # @option opts [String] :content_type Sets the Content-Type + # header stored with the object. + # + # @option opts [String] :content_disposition Sets the + # Content-Disposition header stored with the object. + # + # @option opts [String] :expires_header Sets the Expires + # header stored with the object. + # + # @option options [Symbol] :acl A canned access control + # policy. Valid values are: + # * +:private+ + # * +:public_read+ + # * +:public_read_write+ + # * +:authenticated_read+ + # * +:bucket_owner_read+ + # * +:bucket_owner_full_control+ + # + # @option opts [String] :success_action_redirect The URL to + # which the client is redirected upon successful upload. + # + # @option opts [Integer] :success_action_status The status + # code returned to the client upon successful upload if + # +:success_action_redirect+ is not specified. Accepts the + # values 200, 201, or 204 (default). + # + # If the value is set to 200 or 204, Amazon S3 returns an + # empty document with a 200 or 204 status code. + # + # If the value is set to 201, Amazon S3 returns an XML + # document with a 201 status code. For information on the + # content of the XML document, see + # {POST Object}[http://docs.amazonwebservices.com/AmazonS3/2006-03-01/API/index.html?RESTObjectPOST.html]. + # + # @option opts [Hash] :metadata A hash of the metadata fields + # included in the signed fields. Additional metadata fields + # may be provided with the upload as long as they satisfy + # the conditions specified for the upload (see {#where}). + # + # @option opts [Integer, Range] :content_length The range of + # acceptable object sizes for the upload. By default any + # size object may be uploaded. + # + # @option opts [Array] :ignore Additional fields which + # may be sent with the upload. These will be included in + # the policy so that they can be sent with any value. S3 + # will ignore them. + def initialize(bucket, opts = {}) + @bucket = bucket + @key = opts[:key] + @secure = (opts[:secure] != false) + @fields = {} + SPECIAL_FIELDS.each do |name| + @fields[name] = opts[name] if opts.key?(name) + end + @metadata = opts[:metadata] || {} + @content_length = range_value(opts[:content_length]) + @conditions = opts[:conditions] || {} + @ignored_fields = [opts[:ignore]].flatten.compact + @expires = opts[:expires] + + super + end + + # @return [Boolean] True if {#url} generates an HTTPS url. + def secure? + @secure + end + + # @return [URI::HTTP, URI::HTTPS] The URL to which the form + # fields should be POSTed. If you are using the fields in + # an HTML form, this is the URL to put in the +action+ + # attribute of the form tag. + def url + req = Request.new + req.bucket = bucket.name + req.host = config.s3_endpoint + build_uri(req) + end + + # Lets you specify conditions on a field. See + # {PresignedPost#where} for usage examples. + class ConditionBuilder + + # @private + def initialize(post, field) + @post = post + @field = field + end + + # Specifies that the value of the field must equal the + # provided value. + def is(value) + if @field == :content_length + self.in(value) + else + @post.with_equality_condition(@field, value) + end + end + + # Specifies that the value of the field must begin with the + # provided value. If you are specifying a condition on the + # "key" field, note that this check takes place after the + # +${filename}+ variable is expanded. This is only valid + # for the following fields: + # + # * +:key+ + # * +:cache_control+ + # * +:content_type+ + # * +:content_disposition+ + # * +:content_encoding+ + # * +:expires_header+ + # * +:acl+ + # * +:success_action_redirect+ + # * metadata fields (see {#where_metadata}) + def starts_with(prefix) + @post.with_prefix_condition(@field, prefix) + end + + # Specifies that the value of the field must be in the given + # range. This may only be used to constrain the + # +:content_length+ field, + # e.g. presigned_post.with(:conent_length).in(1..4). + def in(range) + @post.refine(:content_length => range) + end + + end + + # Adds a condition to the policy for the POST. Use + # {#where_metadata} to add metadata conditions. + # + # @example Restricting the ACL to "bucket-owner" ACLs + # presigned_post.where(:acl).starts_with("bucket-owner") + # + # @param [Symbol] field The field for which a condition should + # be added. Valid values: + # + # * +:key+ + # * +:content_length+ + # * +:cache_control+ + # * +:content_type+ + # * +:content_disposition+ + # * +:content_encoding+ + # * +:expires_header+ + # * +:acl+ + # * +:success_action_redirect+ + # * +:success_action_status+ + # + # @return [ConditionBuilder] An object that allows you to + # specify a condition on the field. + def where(field) + raise ArgumentError.new("unrecognized field name #{field}") unless + [:key, :content_length, *SPECIAL_FIELDS].include?(field) or + field =~ /^x-amz-meta-/ + ConditionBuilder.new(self, field) + end + + # Adds a condition to the policy for the POST to constrain the + # values of metadata fields uploaded with the object. If a + # metadata field does not have a condition associated with it + # and is not specified in the constructor (see {#metadata}) + # then S3 will reject it. + # + # @param [Symbol, String] field The name of the metadata + # attribute. For example, +:color+ corresponds to the + # "x-amz-meta-color" field in the POST body. + # + # @return [ConditionBuilder] An object that allows you to + # specify a condition on the metadata attribute. + def where_metadata(field) + where("x-amz-meta-#{field}") + end + + # @return [String] The Base64-encoded JSON policy document. + def policy + json = { + "expiration" => format_expiration, + "conditions" => generate_conditions + }.to_json + Base64.encode64(json) + end + + # @return [Hash] A collection of form fields (including a + # signature and a policy) that can be used to POST data to + # S3. Additional form fields may be added after the fact as + # long as they are described by a policy condition (see + # {#where}). + def fields + signature = + config.signer.sign(policy, "sha1") + + { + "AWSAccessKeyId" => config.signer.access_key_id, + "key" => key, + "policy" => policy, + "signature" => signature + }.merge(optional_fields) + end + + # @private + def with_equality_condition(option_name, value) + field_name = field_name(option_name) + with_condition(option_name, Hash[[[field_name, value]]]) + end + + # @private + def with_prefix_condition(option_name, prefix) + field_name = field_name(option_name) + with_condition(option_name, + ["starts-with", "$#{field_name}", prefix]) + end + + # @private + def refine(opts) + self.class.new(bucket, { + :conditions => conditions, + :key => key, + :metadata => metadata, + :secure => secure?, + :content_length => content_length + }.merge(@fields). + merge(opts)) + end + + # @private + private + def with_condition(field, condition) + conditions = self.conditions.dup + (conditions[field] ||= []) << condition + refine(:conditions => conditions) + end + + # @private + private + def format_expiration + time = expires || Time.now.utc + 60*60 + time = + case time + when Time + time + when DateTime + Time.parse(time.to_s) + when Integer + (Time.now + time) + when String + Time.parse(time) + end + time.utc.iso8601 + end + + # @private + private + def range_value(range) + case range + when Integer + range..range + when Range + range + end + end + + # @private + private + def split_range(range) + range = range_value(range) + [range.begin, + (range.exclude_end? ? + range.end-1 : + range.end)] + end + + # @private + private + def optional_fields + fields = (SPECIAL_FIELDS & + @fields.keys).inject({}) do |fields, option_name| + fields[field_name(option_name)] = + @fields[option_name].to_s + fields + end + + fields["acl"] = fields["acl"].tr("_", "-") if + fields["acl"] + + @metadata.each do |key, value| + fields["x-amz-meta-#{key}"] = value.to_s + end + + fields + end + + # @private + private + def field_name(option_name) + case option_name + when :expires_header + "Expires" + when :acl, :success_action_redirect, :success_action_status + option_name.to_s + else + # e.g. Cache-Control from cache_control + field_name = option_name.to_s.tr("_", "-"). + gsub(/-(.)/) { |m| m.upcase } + field_name[0,1] = field_name[0,1].upcase + field_name + end + end + + # @private + private + def generate_conditions + conditions.inject([]) do |ary, (field, field_conds)| + ary += field_conds + end + + [{ "bucket" => bucket.name }] + + key_conditions + + optional_fields.map { |(n, v)| Hash[[[n, v]]] } + + range_conditions + + ignored_conditions + end + + # @private + private + def ignored_conditions + ignored_fields.map do |field| + ["starts-with", "$#{field}", ""] + end + end + + # @private + private + def range_conditions + if content_length + [["content-length-range", *split_range(content_length)]] + else + [] + end + end + + # @private + private + def key_conditions + [if key && key.include?("${filename}") + ["starts-with", "$key", key[/^(.*)\$\{filename\}/, 1]] + elsif key + { "key" => key } + else + ["starts-with", "$key", ""] + end] + end + + # @private + private + def build_uri(request) + uri_class = secure? ? URI::HTTPS : URI::HTTP + uri_class.build(:host => request.host, + :path => request.path, + :query => request.querystring) + end + + end + + end +end diff --git a/lib/aws/s3/request.rb b/lib/aws/s3/request.rb new file mode 100644 index 00000000000..da11f23ce26 --- /dev/null +++ b/lib/aws/s3/request.rb @@ -0,0 +1,198 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/http/request' +require 'aws/base_client' +require 'uri' +require 'time' + +module AWS + class S3 + + # @private + class Request < AWS::Http::Request + + # @param [bucket] S3 bucket name + attr_accessor :bucket + + # @param [String] S3 object key + attr_accessor :key + + attr_accessor :body_stream + + def metadata= metadata + Array(metadata).each do |name, value| + headers["x-amz-meta-#{name}"] = value + end + end + + def canned_acl= acl + if acl.kind_of?(Symbol) + headers["x-amz-acl"] = acl.to_s.gsub("_", "-") + elsif acl + headers["x-amz-acl"] = acl + end + end + + def storage_class= storage_class + if storage_class.kind_of?(Symbol) + headers["x-amz-storage-class"] = storage_class.to_s.upcase + elsif storage_class + headers["x-amz-storage-class"] = storage_class + end + end + + def host + Client.dns_compatible_bucket_name?(bucket) ? + "#{bucket}.#{@host}" : + @host + end + + def path + parts = [] + unless bucket.nil? or Client.dns_compatible_bucket_name?(bucket) + parts << bucket + end + parts << key if key + "/#{parts.join('/')}" + end + + def querystring + url_encoded_params + end + + # @param [String, IO] The http request body. This can be a string or + # any object that responds to #read and #eof? (like an IO object). + def body= body + @body_stream = StringIO.new(body) + end + + # @return [String, nil] The http request body. + def body + if @body_stream + string = @body_stream.read + @body_stream.rewind + string + else + nil + end + end + + # From the S3 developer guide: + # + # StringToSign = + # HTTP-Verb + "\n" + + # content-md5 + "\n" + + # content-type + "\n" + + # date + "\n" + + # CanonicalizedAmzHeaders + CanonicalizedResource; + # + def string_to_sign + [ + http_method, + headers.values_at('content-md5', 'content-type').join("\n"), + signing_string_date, + canonicalized_headers, + canonicalized_resource, + ].flatten.compact.join("\n") + end + + def signing_string_date + # if a date is provided via x-amz-date then we should omit the + # Date header from the signing string (should appear as a blank line) + if headers.detect{|k,v| k.to_s =~ /^x-amz-date$/i } + '' + else + headers['date'] ||= Time.now.rfc822 + end + end + + # From the S3 developer guide + # + # CanonicalizedResource = + # [ "/" + Bucket ] + + # + + # [ sub-resource, if present. e.g. "?acl", "?location", + # "?logging", or "?torrent"]; + # + def canonicalized_resource + + parts = [] + + # virtual hosted-style requests require the hostname to appear + # in the canonicalized resource prefixed by a forward slash. + if Client.dns_compatible_bucket_name?(bucket) + parts << "/#{bucket}" + end + + # all requests require the portion of the un-decoded uri up to + # but not including the query string + parts << path + + # lastly any sub resource querystring params need to be appened + # in lexigraphical ordered joined by '&' and prefixed by '?' + params = (sub_resource_params + + query_parameters_for_signature) + unless params.empty? + parts << '?' + parts << params.sort.collect{|p| p.to_s }.join('&') + end + + parts.join + end + + # CanonicalizedAmzHeaders + # + # See the developer guide for more information on how this element + # is generated. + # + def canonicalized_headers + x_amz = headers.select{|name, value| name.to_s =~ /^x-amz-/i } + x_amz = x_amz.collect{|name, value| [name.downcase, value] } + x_amz = x_amz.sort_by{|name, value| name } + x_amz = x_amz.collect{|name, value| "#{name}:#{value}" }.join("\n") + x_amz == '' ? nil : x_amz + end + + def sub_resource_params + params.select{|p| self.class.sub_resources.include?(p.name) } + end + + def query_parameters_for_signature + params.select { |p| self.class.query_parameters.include?(p.name) } + end + + def add_authorization!(signer) + signature = URI.escape(signer.sign(string_to_sign, 'sha1')) + headers["authorization"] = "AWS #{signer.access_key_id}:#{signature}" + end + + class << self + + def sub_resources + %w(acl location logging notification partNumber policy + requestPayment torrent uploadId uploads versionId + versioning versions) + end + + def query_parameters + %w(response-content-type response-content-language + response-expires response-cache-control + response-content-disposition response-content-encoding) + end + + end + + end + end +end diff --git a/lib/aws/s3/s3_object.rb b/lib/aws/s3/s3_object.rb new file mode 100644 index 00000000000..0dc0b344fa0 --- /dev/null +++ b/lib/aws/s3/s3_object.rb @@ -0,0 +1,794 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/request' +require 'aws/s3/object_metadata' +require 'aws/s3/multipart_upload' +require 'aws/s3/object_upload_collection' +require 'aws/s3/presigned_post' +require 'aws/s3/data_options' +require 'uri' + +module AWS + class S3 + + # Represents an object in S3 identified by a key. + # + # object = bucket.objects["key-to-my-object"] + # object.key #=> 'key-to-my-object' + # + # See {ObjectCollection} for more information on finding objects. + # + # == Writing and Reading S3Objects + # + # obj = bucket.objects["my-text-object"] + # + # obj.write("MY TEXT") + # obj.read + # #=> "MY TEXT" + # + # obj.write(File.new("README.txt")) + # obj.read + # # should equal File.read("README.txt") + # + class S3Object + + include Model + include DataOptions + + # @param [Bucket] bucket The bucket this object belongs to. + # @param [String] key The object's key. + def initialize(bucket, key, opts = {}) + super + @key = key + @bucket = bucket + end + + # @return [String] The objects unique key + attr_reader :key + + # @return [Bucket] The bucket this object is in. + attr_reader :bucket + + # @private + def inspect + "<#{self.class}:#{bucket.name}/#{key}>" + end + + # @return [Boolean] Returns true if the other object belongs to the + # same bucket and has the same key. + def ==(other) + other.kind_of?(S3Object) and other.bucket == bucket and other.key == key + end + + alias_method :eql?, :== + + # Performs a HEAD request against this object and returns an object + # with useful information about the object, including: + # + # * metadata (hash of user-supplied key-value pairs) + # * content_length (integer, number of bytes) + # * content_type (as sent to S3 when uploading the object) + # * etag (typically the object's MD5) + # + # @param [Hash] options + # @option options [String] :version_id Which version of this object + # to make a HEAD request against. + # @return [Response] A head object response with metatadata, + # content_length, content_type and etag. + def head options = {} + client.head_object(options.merge( + :bucket_name => bucket.name, :key => key)) + end + + # Returns the object's ETag. + # + # Generally the ETAG is the MD5 of the object. If the object was + # uploaded using multipart upload then this is the MD5 all of the + # upload-part-md5s. + # + # @return [String] Returns the object's ETag + def etag + head.etag + end + + # @return [Integer] Size of the object in bytes. + def content_length + head.content_length + end + + # @note S3 does not compute content-type. It reports the content-type + # as was reported during the file upload. + # @return [String] Returns the content type as reported by S3, + # defaults to an empty string when not provided during upload. + def content_type + head.content_type + end + + # Deletes the object from its S3 bucket. + # + # @param [Hash] options + # @option [String] :version_id (nil) If present the specified version + # of this object will be deleted. Only works for buckets that have + # had versioning enabled. + # @return [Response] + def delete options = {} + options[:bucket_name] = bucket.name + options[:key] = key + client.delete_object(options) + end + + # @option [String] :version_id (nil) If present the metadata object + # will be for the specified version. + # @return [ObjectMetadata] Returns an instance of ObjectMetadata + # representing the metadata for this object. + def metadata options = {} + options[:config] = config + ObjectMetadata.new(self, options) + end + + # Returns a colletion representing all the object versions + # for this object. + # + # bucket.versioning_enabled? # => true + # version = bucket.objects["mykey"].versions.latest + # + # @return [ObjectVersionCollection] + def versions + ObjectVersionCollection.new(self) + end + + # Writes data to the object in S3. This method will attempt + # to intelligently choose between uploading in one request and + # using {#multipart_upload}. + # + # Unless versioning is enabled, any data currently in S3 at {#key} + # will be replaced. + # + # You can pass +:data+ or +:file+ as the first argument or as + # options. Example usage: + # + # obj = s3.buckets.mybucket.objects.mykey + # obj.write("HELLO") + # obj.write(:data => "HELLO") + # obj.write(Pathname.new("myfile")) + # obj.write(:file => "myfile") + # + # # writes zero-length data + # obj.write(:metadata => { "avg-rating" => "5 stars" }) + # + # @overload write(options = {}) + # @overload write(data, options = {}) + # + # @param data The data to upload (see the +:data+ + # option). + # + # @param options [Hash] Additional upload options. + # + # @option options :data The data to upload. Valid values include: + # * A string + # + # * A Pathname object + # + # * Any object responding to +read+ and +eof?+; the object + # must support the following access methods: + # read # all at once + # read(length) until eof? # in chunks + # + # If you specify data this way, you must also include the + # +:content_length+ option. + # + # @option options [String] :file Can be specified instead of +:data+; + # its value specifies the path of a file to upload. + # + # @option options [Boolean] :single_request If this option is + # true, the method will always generate exactly one request + # to S3 regardless of how much data is being uploaded. + # + # @option options [Integer] :content_length If provided, this + # option must match the total number of bytes written to S3 + # during the operation. This option is required if +:data+ + # is an IO-like object without a +size+ method. + # + # @option options [Integer] :multipart_threshold Specifies the + # maximum size in bytes of a single-request upload. If the + # data being uploaded is larger than this threshold, it will + # be uploaded using {#multipart_upload}. + # + # @option options [Integer] :multipart_min_part_size The + # minimum size of a part if a multi-part upload is used. S3 + # will reject non-final parts smaller than 5MB, and the + # default for this option is 5MB. + # + # @option options [Hash] :metadata A hash of metadata to be + # included with the object. These will be sent to S3 as + # headers prefixed with +x-amz-meta+. + # + # @option options [Symbol] :acl A canned access control + # policy. Valid values are: + # * +:private+ + # * +:public_read+ + # * +:public_read_write+ + # * +:authenticated_read+ + # * +:bucket_owner_read+ + # * +:bucket_owner_full_control+ + # + # @option options [Symbol] :storage_class Controls whether + # Reduced Redundancy Storage is enabled for the object. + # Valid values are +:standard+ (the default) or + # +:reduced_redundancy+. + # + # @option options :cache_control [String] Can be used to specify + # caching behavior. See + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + # + # @option options :content_disposition [String] Specifies + # presentational information for the object. See + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec19.5.1 + # + # @option options :content_encoding [String] Specifies what + # content encodings have been applied to the object and thus + # what decoding mechanisms must be applied to obtain the + # media-type referenced by the +Content-Type+ header field. + # See + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 + # + # @option options :content_type A standard MIME type + # describing the format of the object data. + # + # @return [S3Object, ObjectVersion] If the bucket has versioning + # enabled, returns the {ObjectVersion} representing the + # version that was uploaded. If versioning is disabled, + # returns self. + def write(options_or_data = nil, options = nil) + + (data_options, put_options) = + compute_put_options(options_or_data, options) + + if use_multipart?(data_options, put_options) + put_options.delete(:multipart_threshold) + multipart_upload(put_options) do |upload| + each_part(data_options, put_options) do |part| + upload.add_part(part) + end + end + else + opts = { :bucket_name => bucket.name, :key => key } + resp = client.put_object(opts.merge(put_options).merge(data_options)) + if resp.version_id + ObjectVersion.new(self, resp.version_id) + else + self + end + end + end + + # Performs a multipart upload. Use this if you have specific + # needs for how the upload is split into parts, or if you want + # to have more control over how the failure of an individual + # part upload is handled. Otherwise, {#write} is much simpler + # to use. + # + # @example Uploading an object in two parts + # bucket.objects.myobject.multipart_upload do |upload| + # upload.add_part("a" * 5242880) + # upload.add_part("b" * 2097152) + # end + # + # @example Uploading parts out of order + # bucket.objects.myobject.multipart_upload do |upload| + # upload.add_part("b" * 2097152, :part_number => 2) + # upload.add_part("a" * 5242880, :part_number => 1) + # end + # + # @example Aborting an upload after parts have been added + # bucket.objects.myobject.multipart_upload do |upload| + # upload.add_part("b" * 2097152, :part_number => 2) + # upload.abort + # end + # + # @example Starting an upload and completing it later by ID + # upload = bucket.objects.myobject.multipart_upload + # upload.add_part("a" * 5242880) + # upload.add_part("b" * 2097152) + # id = upload.id + # + # # later or in a different process + # upload = bucket.objects.myobject.multipart_uploads[id] + # upload.complete(:remote_parts) + # + # @yieldparam [MultipartUpload] upload A handle to the upload. + # {MultipartUpload#close} is called in an +ensure+ clause so + # that the upload will always be either completed or + # aborted. + # + # @param [Hash] options Options for the upload. + # + # @option options [Hash] :metadata A hash of metadata to be + # included with the object. These will be sent to S3 as + # headers prefixed with +x-amz-meta+. + # + # @option options [Symbol] :acl A canned access control + # policy. Valid values are: + # * +:private+ + # * +:public_read+ + # * +:public_read_write+ + # * +:authenticated_read+ + # * +:bucket_owner_read+ + # * +:bucket_owner_full_control+ + # + # @option options [Boolean] :reduced_redundancy If true, + # Reduced Redundancy Storage will be enabled for the + # uploaded object. + # + # @option options :cache_control [String] Can be used to specify + # caching behavior. See + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + # + # @option options :content_disposition [String] Specifies + # presentational information for the object. See + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec19.5.1 + # + # @option options :content_encoding [String] Specifies what + # content encodings have been applied to the object and thus + # what decoding mechanisms must be applied to obtain the + # media-type referenced by the +Content-Type+ header field. + # See + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 + # + # @option options :content_type A standard MIME type + # describing the format of the object data. + # + # @return [S3Object, ObjectVersion] If the bucket has versioning + # enabled, returns the {ObjectVersion} representing the + # version that was uploaded. If versioning is disabled, + # returns self. + def multipart_upload(options = {}) + upload = multipart_uploads.create(options) + + if block_given? + result = nil + begin + yield(upload) + ensure + result = upload.close + end + result + else + upload + end + end + + # @example Abort any in-progress uploads for the object: + # + # object.multipart_uploads.each(&:abort) + # + # @return [ObjectUploadCollection] Returns an object representing the + # collection of uploads that are in progress for this object. + def multipart_uploads + ObjectUploadCollection.new(self) + end + + # Copies data from one S3 object to another. + # + # S3 handles the copy so the clients does not need to fetch the data + # and upload it again. You can also change the storage class and + # metadata of the object when copying. + # + # @param [Mixed] source + # @param [Hash] options + # @option options [String] :bucket_name The name of the bucket + # the source object can be found in. Defaults to the current + # object's bucket. + # @option options [Bucket] :bucket The bucket the source object can + # be found in. Defaults to the current object's bucket. + # @option options [Hash] :metadata A hash of metadata to save with + # the copied object. When blank, the sources metadata is copied. + # @option options [Boolean] :reduced_redundancy (false) If true the + # object is stored with reduced redundancy in S3 for a lower cost. + # @option options [String] :version_id (nil) Causes the copy to + # read a specific version of the source object. + # @return [nil] + def copy_from source, options = {} + + copy_opts = { :bucket_name => bucket.name, :key => key } + + copy_opts[:copy_source] = case source + when S3Object + "#{source.bucket.name}/#{source.key}" + when ObjectVersion + copy_opts[:version_id] = source.version_id + "#{source.object.bucket.name}/#{source.object.key}" + else + case + when options[:bucket] then "#{options[:bucket].name}/#{source}" + when options[:bucket_name] then "#{options[:bucket_name]}/#{source}" + else "#{self.bucket.name}/#{source}" + end + end + + if options[:metadata] + copy_opts[:metadata] = options[:metadata] + copy_opts[:metadata_directive] = 'REPLACE' + else + copy_opts[:metadata_directive] = 'COPY' + end + + copy_opts[:version_id] = options[:version_id] if options[:version_id] + + copy_opts[:storage_class] = 'REDUCED_REDUNDANCY' if + options[:reduced_redundancy] + + client.copy_object(copy_opts) + + nil + + end + + # Copies data from the current object to another object in S3. + # + # S3 handles the copy so the client does not need to fetch the data + # and upload it again. You can also change the storage class and + # metadata of the object when copying. + # + # @param [S3Object,String] target An S3Object, or a string key of + # and object to copy to. + # @param [Hash] options + # @option options [String] :bucket_name The name of the bucket + # the object should be copied into. Defaults to the current object's + # bucket. + # @option options [Bucket] :bucket The bucket the target object + # should be copied into. Defaults to the current object's bucket. + # @option options [Hash] :metadata A hash of metadata to save with + # the copied object. When blank, the sources metadata is copied. + # @option options [Boolean] :reduced_redundancy (false) If true the + # object is stored with reduced redundancy in S3 for a lower cost. + # @return (see #copy_from) + def copy_to target, options = {} + + unless target.is_a?(S3Object) + + bucket = case + when options[:bucket] then options[:bucket] + when options[:bucket_name] + Bucket.new(options[:bucket_name], :config => config) + else self.bucket + end + + target = S3Object.new(bucket, target) + end + + copy_opts = options.dup + copy_opts.delete(:bucket) + copy_opts.delete(:bucket_name) + + target.copy_from(self, copy_opts) + + end + + # Fetches the object data from S3. + # + # @example Reading data as a string + # object.write('some data') + # object.read + # #=> 'some data' + # + # @param [Hash] options + # @option options [String] :version_id Reads data from a + # specific version of this object. + # @option options [Time] :if_unmodified_since Causes #read + # to return nil if the object was modified since the + # given time. + # @option options [Time] :if_modified_since Causes #read + # to return nil unless the object was modified since the + # given time. + # @option options [String] :if_match If specified, the method + # will return nil (and not fetch any data) unless the object ETag + # @option options [Range] :range A byte range to read data from + def read(options = {}, &blk) + options[:bucket_name] = bucket.name + options[:key] = key + client.get_object(options).data + end + + # @private + module ACLProxy + + attr_accessor :object + + def change + yield(self) + object.acl = self + end + + end + + # Returns the object's access control list. This will be an + # instance of AccessControlList, plus an additional +change+ + # method: + # + # object.acl.change do |acl| + # # remove any grants to someone other than the bucket owner + # owner_id = object.bucket.owner.id + # acl.grants.reject! do |g| + # g.grantee.canonical_user_id != owner_id + # end + # end + # + # Note that changing the ACL is not an atomic operation; it + # fetches the current ACL, yields it to the block, and then + # sets it again. Therefore, it's possible that you may + # overwrite a concurrent update to the ACL using this + # method. + # + # @return [AccessControlList] + # + def acl + acl = client.get_object_acl( + :bucket_name => bucket.name, + :key => key + ).acl + acl.extend ACLProxy + acl.object = self + acl + end + + # Sets the object's access control list. +acl+ can be: + # * An XML policy as a string (which is passed to S3 uninterpreted) + # * An AccessControlList object + # * Any object that responds to +to_xml+ + # * Any Hash that is acceptable as an argument to + # AccessControlList#initialize. + # + # @param (see Bucket#acl=) + # @return [Response] + # + def acl=(acl) + client.set_object_acl( + :bucket_name => bucket.name, + :key => key, + :acl => acl) + end + + # @private + REQUEST_PARAMETERS = Request.query_parameters.map do |p| + p.tr("-","_").to_sym + end + + # Generates a presigned URL for an operation on this object. + # This URL can be used by a regular HTTP client to perform the + # desired operation without credentials and without changing + # the permissions of the object. + # + # @example Generate a url to read an object + # bucket.objects.myobject.url_for(:read) + # + # @example Generate a url to delete an object + # bucket.objects.myobject.url_for(:delete) + # + # @example Override response headers for reading an object + # object = bucket.objects.myobject + # url = object.url_for(:read, :response_content_type => "application/json") + # + # @example Generate a url that expires in 10 minutes + # bucket.objects.myobject.url_for(:read, :expires => 10*60) + # + # @param [Symbol, String] method The HTTP verb or object + # method for which the returned URL will be valid. Valid + # values: + # + # * +:get+ or +:read+ + # * +:put+ or +:write+ + # * +:delete+ + # + # @param [Hash] options Additional options for generating the URL. + # + # @option options :expires Sets the expiration time of the + # URL; after this time S3 will return an error if the URL is + # used. This can be an integer (to specify the number of + # seconds after the current time), a string (which is parsed + # as a date using Time#parse), a Time, or a DateTime object. + # This option defaults to one hour after the current time. + # + # @option options [String] :secure Whether to generate a + # secure (HTTPS) URL or a plain HTTP url. + # + # @option options [String] :response_content_type Sets the + # Content-Type header of the response when performing an + # HTTP GET on the returned URL. + # + # @option options [String] :response_content_language Sets the + # Content-Language header of the response when performing an + # HTTP GET on the returned URL. + # + # @option options [String] :response_expires Sets the Expires + # header of the response when performing an HTTP GET on the + # returned URL. + # + # @option options [String] :response_cache_control Sets the + # Cache-Control header of the response when performing an + # HTTP GET on the returned URL. + # + # @option options [String] :response_content_disposition Sets + # the Content-Disposition header of the response when + # performing an HTTP GET on the returned URL. + # + # @option options [String] :response_content_encoding Sets the + # Content-Encoding header of the response when performing an + # HTTP GET on the returned URL. + # @return [URI::HTTP, URI::HTTPS] + def url_for(method, options = {}) + req = request_for_signing(options) + + method = http_method(method) + expires = expiration_timestamp(options[:expires]) + req.add_param("AWSAccessKeyId", config.signer.access_key_id) + req.add_param("Signature", signature(method, expires, req)) + req.add_param("Expires", expires) + + build_uri(options[:secure] != false, req) + end + + # Generates a public (not authenticated) URL for the object. + # + # @param [Hash] options Options for generating the URL. + # + # @option options [Boolean] :secure Whether to generate a + # secure (HTTPS) URL or a plain HTTP url. + # @return [URI::HTTP, URI::HTTPS] + def public_url(options = {}) + req = request_for_signing(options) + build_uri(options[:secure] != false, req) + end + + # Generates fields for a presigned POST to this object. This + # method adds a constraint that the key must match the key of + # this object. All options are sent to the PresignedPost + # constructor. + # + # @see PresignedPost + # @return [PresignedPost] + def presigned_post(options = {}) + PresignedPost.new(bucket, options.merge(:key => key)) + end + + # @private + private + def build_uri(secure, request) + uri_class = secure ? URI::HTTPS : URI::HTTP + uri_class.build(:host => request.host, + :path => request.path, + :query => request.querystring) + end + + # @private + private + def signature(method, expires, request) + string_to_sign = [method, + "", "", + expires, + request.canonicalized_resource].join("\n") + config.signer.sign(string_to_sign, "sha1") + end + + # @private + private + def expiration_timestamp(input) + case input + when Time + expires = input.to_i + when DateTime + expires = Time.parse(input.to_s).to_i + when Integer + expires = (Time.now + input).to_i + when String + expires = Time.parse(input).to_i + else + expires = (Time.now + 60*60).to_i + end + end + + # @private + private + def http_method(input) + symbol = case input + when :read then :get + when :write then :put + else + input + end + symbol.to_s.upcase + end + + # @private + private + def request_for_signing(options) + req = Request.new + + req.bucket = bucket.name + req.key = key + req.host = config.s3_endpoint + + REQUEST_PARAMETERS.each do |param| + req.add_param(param.to_s.tr("_","-"), + options[param]) if options.key?(param) + end + + req + end + + # @private + private + def compute_put_options(options_or_data, options) + put_opts = {} + + if options + + raise ArgumentError.new("Object data passed twice (argument and file path)") if + options[:file] + + raise ArgumentError.new("Object data passed twice (argument and option)") if + options[:data] + + [{ :data => options_or_data }, options] + elsif options_or_data.kind_of?(Hash) + if file = options_or_data[:file] + data_options = { :file => file } + else + data_options = { :data => options_or_data[:data] || "" } + end + + [data_options, options_or_data] + else + [{ :data => options_or_data || "" }, {}] + end + end + + # @private + private + def use_multipart?(data_options, options) + threshold = options[:multipart_threshold] || + config.s3_multipart_threshold + + !options[:single_request] and + size = content_length_from(data_options.merge(options)) and + size > threshold + end + + # @private + private + def each_part(data_options, options) + total_size = content_length_from(data_options.merge(options)) + part_size = optimal_part_size(total_size, options) + stream = data_stream_from(data_options.merge(options)) + while !stream.eof? + yield(stream.read(part_size)) + end + end + + # @private + private + def optimal_part_size(total_size, options) + maximum_parts = config.s3_multipart_max_parts + min_size = options[:multipart_min_part_size] || + config.s3_multipart_min_part_size + + [(total_size.to_f / maximum_parts.to_f).ceil, + min_size].max.to_i + end + + end + + end +end diff --git a/lib/aws/s3/tree.rb b/lib/aws/s3/tree.rb new file mode 100644 index 00000000000..cb354edda47 --- /dev/null +++ b/lib/aws/s3/tree.rb @@ -0,0 +1,116 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/tree/parent' +require 'aws/s3/tree/leaf_node' +require 'aws/s3/tree/branch_node' +require 'aws/s3/tree/child_collection' + +module AWS + class S3 + + # A utility class that supports exploring an S3 {Bucket} like a + # tree. + # + # Frequently objects stored in S3 have keys that look like a filesystem + # directory structure. + # + # Given you have a bucket with the following keys: + # + # README.txt + # videos/wedding.mpg + # videos/family_reunion.mpg + # photos/2010/house.jpg + # photos/2011/fall/leaves.jpg + # photos/2011/summer/vacation.jpg + # photos/2011/summer/family.jpg + # + # You might like to explore the contents of this bucket as a tree: + # + # tree = bucket.as_tree + # + # directories = tree.children.select(&:branch?).collect(&:prefix) + # #=> ['photos', 'videos'] + # + # files = tree.children.select(&:leaf?).collect(&:key) + # #=> ['README.txt'] + # + # If you want to start further down, pass a prefix to {Bucket#as_tree}: + # + # tree = bucket.as_tree(:prefix => 'photos/2011') + # + # directories = tree.children.select(&:branch?).collect(&:prefix) + # #=> ['photos/20011/fall', 'photos/20011/summer'] + # + # files = tree.children.select(&:leaf?).collect(&:key) + # #=> [] + # + # All non-leaf nodes ({Tree} and {Tree::BranchNode} instances) + # have a {Tree::Parent#children} method that provides access to + # the next level of the tree, and all nodes ({Tree}, + # {Tree::BranchNode}, and {Tree::LeafNode}) have a {#parent} + # method that returns the parent node. In our examples above, the + # non-leaf nodes are common prefixes to multiple keys + # (directories) and leaf nodes are object keys. + # + # You can continue crawling the tree using the +children+ + # collection on each branch node, which will contain the branch + # nodes and leaf nodes below it. + # + # You can construct a Tree object using the +as_tree+ method of + # any of the following classes: + # + # * {Bucket} or {ObjectCollection} (for {S3Object} leaf nodes) + # + # * {BucketVersionCollection} (for {ObjectVersion} leaf nodes) + # + # * {MultipartUploadCollection} (for {MultipartUpload} leaf nodes) + # + # The methods to explore the tree are the same for each kind of + # leaf node, but {Tree::LeafNode#member} will return a different + # type of object depending on which kind of collection the tree is + # using. + class Tree + + include Parent + + # @param [ObjectCollection, BucketVersionCollection, + # MultipartUploadCollection] collection The collection whose + # members will be explored using the tree. + # + # @param [Hash] options Additional options for constructing the + # tree. + # + # @option options [String] :prefix (nil) Set prefix to choose + # where the top of the tree will be. A value of +nil+ means + # that the tree will include all objects in the collection. + # + # @option options [String] :delimiter ('/') The string that + # separates each level of the tree. This is usually a + # directory separator. + # + # @option options [Boolean] :append (true) If true, the delimiter is + # appended to the prefix when the prefix does not already end + # with the delimiter. + def initialize collection, options = {} + super + end + + # @return The parent node in the tree. In the case of a Tree, + # the parent is always nil. + def parent; nil; end + + end + end +end diff --git a/lib/aws/s3/tree/branch_node.rb b/lib/aws/s3/tree/branch_node.rb new file mode 100644 index 00000000000..eaf38fd65df --- /dev/null +++ b/lib/aws/s3/tree/branch_node.rb @@ -0,0 +1,71 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/tree/parent' + +module AWS + class S3 + class Tree + + # Represents a branch in an {S3::Tree}. From a branch node you + # can descend deeper into the tree using {Parent#children} or go + # back to the parent node using {#parent}. + # + # When enumerating nodes in an S3 tree keys grouped by a common + # prefix are represented as a branch node. + # + # Branch nodes are often treated like directories. + # + # @see Tree + # @note Generally you do not need to create branch nodes. + class BranchNode < Node + + include Parent + + # @private + def initialize parent, collection, options = {} + @parent = parent + super(collection, + options.merge(:prefix => collection.prefix)) + end + + # @return [Tree, BranchNode] The parent node in the tree. + attr_reader :parent + + # @return [true] + def branch? + true + end + + # @return [false] + def leaf? + false + end + + # Returns a new Tree object that starts at this branch node. + # The returned tree will have the same prefix, delimiter and + # append mode as the tree the branch belongs to. + # + # @return [Tree] + def as_tree + Tree.new(collection, + :prefix => prefix, + :delimiter => delimiter, + :append => append?) + end + + end + end + end +end diff --git a/lib/aws/s3/tree/child_collection.rb b/lib/aws/s3/tree/child_collection.rb new file mode 100644 index 00000000000..0e84299365d --- /dev/null +++ b/lib/aws/s3/tree/child_collection.rb @@ -0,0 +1,108 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/tree/leaf_node' +require 'aws/s3/tree/branch_node' + +module AWS + class S3 + class Tree + + class ChildCollection + + include Model + include Enumerable + + # @private + def initialize parent, collection, options = {} + + options = { + :prefix => nil, + :delimiter => '/', + :append => true, + }.merge(options) + + @parent = parent + @collection = collection + @prefix = options[:prefix] + @delimiter = options[:delimiter] + @append = options[:append] + + super + + end + + # @return [Tree, BranchNode] The parent node in the tree. + attr_reader :parent + + # @return [ObjectCollection, ObjectVersionCollection, + # MultipartUploadCollection] Returns the collection this + # tree is based on. + attr_reader :collection + + # A tree may have a prefix of where in the bucket to be based from. + # @return [String,nil] + attr_reader :prefix + + # When looking at S3 keys as a tree, the delimiter defines what + # string pattern seperates each level of the tree. The delimiter + # defaults to '/' (like in a file system). + # @return [String] + attr_reader :delimiter + + # @return [Boolean] Returns true if the tree is set to auto-append + # the delimiter to the prefix when the prefix does not end with + # the delimiter. + def append? + @append + end + + # Yields up branches and leaves. + # + # A branch node represents a common prefix (like a directory) + # and a leaf node represents a key (S3 object). + # + # @yield [tree_node] Yields up a mixture of branches and leafs. + # @yieldparam [BranchNode,LeafNode] tree_node A branch or a leaf. + # @return [nil] + def each &block + collection = self.collection + if prefix = prefix_with_delim + collection = collection.with_prefix(prefix) + end + collection.each(:delimiter => delimiter) do |member| + case + when member.respond_to?(:key) + yield LeafNode.new(parent, member) + when member.respond_to?(:prefix) + yield BranchNode.new(parent, member, + :delimiter => delimiter, + :append => append?) + end + end + nil + end + + protected + def prefix_with_delim + return prefix unless append? + return nil if prefix.nil? + prefix =~ /#{delimiter}$/ ? prefix : "#{prefix}#{delimiter}" + end + + end + + end + end +end diff --git a/lib/aws/s3/tree/leaf_node.rb b/lib/aws/s3/tree/leaf_node.rb new file mode 100644 index 00000000000..f40ae003c28 --- /dev/null +++ b/lib/aws/s3/tree/leaf_node.rb @@ -0,0 +1,99 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/s3/tree/node' +require 'aws/s3/s3_object' +require 'aws/s3/object_version' +require 'aws/s3/multipart_upload' + +module AWS + class S3 + class Tree + + # Represents a leaf in an {S3::Tree}. + # + # When enumerating nodes in an S3 tree, keys are yielded + # as leaf nodes (they have no children beneath them). + # + # @see Tree + # @note Generally you do not need to create leaf nodes + class LeafNode < Node + + # @private + def initialize parent, member + @parent = parent + @member = member + super() + end + + # @return [Tree, BranchNode] The parent node in the tree. + attr_reader :parent + + # @return [mixed] Returns the object this leaf node represents. + # @see #object + # @see #version + # @see #upload + attr_reader :member + + # @return [String] the key this leaf node represents. + def key + @member.key + end + + # @return [false] + def branch? + false + end + + # @return [true] + def leaf? + true + end + + # @return [S3Object] The object this leaf node represents. + def object + if @member.kind_of?(S3Object) + @member + else + @member.object + end + end + + # @return [ObjectVersion] Returns the object version this leaf + # node represents. + def version + if @member.kind_of?(ObjectVersion) + @member + else + raise "This leaf does not represent a version" + end + end + + # @return [MultipartUpload] Returns the object version this leaf + # node represents. + def upload + if @member.kind_of?(MultipartUpload) + @member + else + raise "This leaf does not represent an upload" + end + end + + def inspect + "<#{self.class}:#{@member.bucket.name}:#{key}>" + end + + end + end + end +end diff --git a/lib/aws/s3/tree/node.rb b/lib/aws/s3/tree/node.rb new file mode 100644 index 00000000000..5c458b2fa2d --- /dev/null +++ b/lib/aws/s3/tree/node.rb @@ -0,0 +1,22 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + class S3 + class Tree + # @private + class Node + end + end + end +end diff --git a/lib/aws/s3/tree/parent.rb b/lib/aws/s3/tree/parent.rb new file mode 100644 index 00000000000..8d5a6a81349 --- /dev/null +++ b/lib/aws/s3/tree/parent.rb @@ -0,0 +1,90 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/tree/node' + +module AWS + class S3 + + class Tree + + # Common methods for tree nodes that are parents to other nodes + # ({Tree} and {BranchNode}). + module Parent + + include Model + + # @private + def initialize collection, options = {} + + options = { + :prefix => nil, + :delimiter => '/', + :append => true, + }.merge(options) + + @collection = collection + @prefix = options[:prefix] + @delimiter = options[:delimiter] + @append = options[:append] + + super + + end + + # @return [ObjectCollection, BucketVersionCollection, + # MultipartUploadCollection] The collection whose members + # will be explored using the tree. + attr_reader :collection + + # A tree may have a prefix of where in the bucket to be based + # from. A value of +nil+ means that the tree will include all + # objects in the collection. + # + # @return [String,nil] + attr_reader :prefix + + # When looking at S3 keys as a tree, the delimiter defines what + # string pattern seperates each level of the tree. The delimiter + # defaults to '/' (like in a file system). + # + # @return [String] + attr_reader :delimiter + + # @return [Boolean] Returns true if the tree is set to auto-append + # the delimiter to the prefix when the prefix does not end with + # the delimiter. + def append? + @append + end + + # @return [Tree::ChildCollection] A collection representing all + # the child nodes of this node. These may be either + # {Tree::BranchNode} objects or {Tree::LeafNode} objects. + def children + Tree::ChildCollection.new(self, collection, + :delimiter => delimiter, + :prefix => prefix, + :append => append?) + end + + def inspect + "<#{self.class}:#{collection.bucket.name}:#{prefix}>" + end + + end + + end + end +end diff --git a/lib/aws/s3/uploaded_part.rb b/lib/aws/s3/uploaded_part.rb new file mode 100644 index 00000000000..5cf84508e65 --- /dev/null +++ b/lib/aws/s3/uploaded_part.rb @@ -0,0 +1,82 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' + +module AWS + class S3 + + # Represents a part of a multipart upload that has been uploaded + # to S3. + # + # @example Get the total size of the uploaded parts + # upload.parts.inject(0) { |sum, part| sum + part.size } + class UploadedPart + + include Model + + # @return [MultipartUpload] The upload to which this belongs. + attr_reader :upload + + # @return [Integer] The part number. + attr_reader :part_number + + # @private + def initialize(upload, part_number, opts = {}) + @upload = upload + @part_number = part_number + super + end + + def ==(other) + other.kind_of?(UploadedPart) and + other.upload == upload and + other.part_number == part_number + end + alias_method :eql?, :== + + # @return [Integer] The size of the part as it currently + # exists in S3. + def size + get_attribute(:size) + end + + # @return [DateTime] The time at which the part was last + # modified. + def last_modified + get_attribute(:last_modified) + end + + # @return [String] The ETag of the part. + def etag + get_attribute(:etag) + end + + # @private + private + def get_attribute(name) + (resp = client.list_parts(:bucket_name => upload.object.bucket.name, + :key => upload.object.key, + :upload_id => upload.id, + :part_number_marker => part_number-1, + :max_parts => 1) and + part = resp.parts.first and + part.part_number == part_number and + part.send(name)) or + raise "part 3 of upload abc123 does not exist" + end + + end + + end +end diff --git a/lib/aws/s3/uploaded_part_collection.rb b/lib/aws/s3/uploaded_part_collection.rb new file mode 100644 index 00000000000..793ffab3b1f --- /dev/null +++ b/lib/aws/s3/uploaded_part_collection.rb @@ -0,0 +1,86 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/s3/paginated_collection' +require 'aws/s3/uploaded_part' + +module AWS + class S3 + + # Represents the collection of parts that have been uploaded for + # a given multipart upload. You can get an instance of this + # class by calling {MultipartUpload#parts}. + # + # @example Get the total size of the uploaded parts + # upload.parts.inject(0) { |sum, part| sum + part.size } + class UploadedPartCollection + + include Enumerable + include Model + include PaginatedCollection + + # @return [MultipartUpload] The upload to which the parts belong. + attr_reader :upload + + # @private + def initialize(upload, opts = {}) + @upload = upload + super + end + + # @return [UploadedPart] An object representing the part with + # the given part number. + # + # @param [Integer] number The part number. + def [](number) + UploadedPart.new(upload, number) + end + + # @private + protected + def each_member_in_page(page, &block) + page.parts.each do |part_info| + part = UploadedPart.new(upload, part_info.part_number) + yield(part) + end + end + + # @private + protected + def list_options(options) + opts = super + opts.merge!(:bucket_name => upload.object.bucket.name, + :key => upload.object.key, + :upload_id => upload.id) + opts + end + + # @private + protected + def limit_param; :max_parts; end + + # @private + protected + def list_request(options) + client.list_parts(options) + end + + # @private + protected + def pagination_markers; [:part_number_marker]; end + + end + + end +end diff --git a/lib/aws/service_interface.rb b/lib/aws/service_interface.rb new file mode 100644 index 00000000000..e17c3bee23d --- /dev/null +++ b/lib/aws/service_interface.rb @@ -0,0 +1,60 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/errors' + +module AWS + + # @private + module ServiceInterface + + def self.included base + + base.send(:attr_reader, :config) + base.send(:attr_reader, :client) + + base.module_eval('module Errors; end') + + unless base::Errors.include?(AWS::Errors) + base::Errors.module_eval { include AWS::Errors } + end + + end + + # Returns a new interface object for this service. You can override + # any of the global configuration parameters by passing them in as + # hash options. They are merged with AWS.config or merged + # with the provided +:config+ object. + # + # @ec2 = AWS::EC2.new(:max_retries => 2) + # + # @see AWS::Cofiguration + # + # @param [Hash] options + # @option options [Configuration] :config An AWS::Configuration + # object to initialize this service interface object with. Defaults + # to AWS.config when not provided. + def initialize options = {} + @config = options[:config] + @config ||= AWS.config + @config = @config.with(options) + @client = config.send(Inflection.ruby_name(self.class.to_s) + '_client') + end + + # @private + def inspect + "<#{self.class}>" + end + + end +end diff --git a/lib/aws/simple_db.rb b/lib/aws/simple_db.rb new file mode 100644 index 00000000000..1ede54a9024 --- /dev/null +++ b/lib/aws/simple_db.rb @@ -0,0 +1,202 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/common' +require 'aws/service_interface' +require 'aws/s3/errors' +require 'aws/simple_db/client' +require 'aws/simple_db/domain_collection' + +module AWS + + # This class is the starting point for working with Amazon SimpleDB. + # + # To use Amazon SimpleDB you must first + # {sign up here}[http://aws.amazon.com/simpledb/]. + # + # For more information about Amazon SimpleDB: + # + # * {Amazon SimpleDB}[http://aws.amazon.com/simpledb/] + # * {Amazon SimpleDB Documentation}[http://aws.amazon.com/documentation/simpledb/] + # + # = Credentials + # + # You can setup default credentials for all AWS services via + # AWS.config: + # + # AWS.config( + # :access_key_id => 'YOUR_ACCESS_KEY_ID', + # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') + # + # Or you can set them directly on the SimpleDB interface: + # + # sdb = AWS::SimpleDB.new( + # :access_key_id => 'YOUR_ACCESS_KEY_ID', + # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') + # + # = Understanding the SimpleDB Interface + # + # SimpleDB stores data in a hierarchy of: + # + # Domains > Items > Attributes + # + # These are modeled with the following classes: + # + # * {DomainCollection} + # * {Domain} + # * {ItemCollection} + # * {Item} + # * {AttributeCollection} + # * {Attribute} + # + # The collection classes listed above make it easy to enumerate, + # the objects they represent. They also make it easy to perform + # bulk operations on all objects in that collection. + # + # = Domains + # + # Domains are like database tables. A domain must exist before you can + # write to it. To create a domain: + # + # sdb = SimpleDB.new + # domain = sdb.domains.create('mydomain') + # + # For more information about working with domains see {DomainCollection} + # and {Domain}. + # + # = Items & Attributes + # + # Items exist in SimpleDB when they have attributes. You can delete an + # item by removing all of its attributes. You create an item by adding + # an attribute to it. + # + # The following example illustrates how you can reference an item that + # does not exist yet: + # + # sdb = SimpleDB.new + # + # # this domain is empty, it has no items + # domain = sdb.domains.create('newdomain') + # domain.items.collect(&:name) + # #=> [] + # + # # this item doesn't exist yet, so it has no attributes + # item = domain.items['newitem'] + # item.attributes.collect(&:name) + # #=> [] + # + # # the item has no attributes + # tags = item.attributes['tags'] + # tags.values + # #=> [] + # + # To create the item in SimpleDB you just need to add an attribute. + # + # tags.add %w(first new) + # + # domain.items.collect(&:name) + # #=> ['newitem'] + # + # item.attributes.collect(&:name) + # #=> ['tags'] + # + # tags.values + # #=> ['first', 'new'] + # + # For more information about working with items and attributes, see: + # + # * {ItemCollection} + # * {Item} + # * {AttributeCollection} + # * {Attribute} + # + # = Lazy Execution + # + # Requests are not made until necessary. This means you can drill down + # all the way to an attribute, by name, without making any requets + # to SimpleDB. + # + # # makes no request to SimpleDB + # sdb = SimpleDB.new + # colors = sdb.domains['mydomain'].items['car'].attributes['colors'] + # + # # one request to get the values for 'colors' + # puts colors.values + # + # # one request to add blue and green + # colors.add 'blue', 'green' + # + # # one request to delete the colors attribute + # colors.delete + # + class SimpleDB + + include ServiceInterface + + # Returns a collection object that represents the domains in your + # account. + # + # @return [DomainCollection] Returns a collection representing all your + # domains. + def domains + DomainCollection.new(:config => config) + end + + # Call this method with a block. Code executed inside the block + # make consistent reads until the block ends. + # + # AWS::SimpleDB.consistent_reads do + # # ... + # end + # + # === Other Modes + # + # You can also use this same function to disable consistent reads insie + # a block. This is useful if you have consistent reads enabled by + # default: + # + # AWS::SimpleDB.consistent_reads(false) do + # # ... + # end + # + # @param [Boolean] state (true) When true, all SimpleDB read operations + # will be consistent reads inside the block. When false, all + # reads operations will not be consistent reads. The previous state + # will be restored after the block executes. + # @return Returns the final block value. + def self.consistent_reads state = true, &block + begin + prev_state = Thread.current['_simple_db_consistent_reads_'] + Thread.current['_simple_db_consistent_reads_'] = state + yield + ensure + Thread.current['_simple_db_consistent_reads_'] = prev_state + end + end + + # @return [Boolean] Returns true if we are inside an AWS::SimpleDB + # #consistent_reads method block. + # @private + def self.in_consistent_reads_block? + !Thread.current['_simple_db_consistent_reads_'].nil? + end + + # @return [Boolean] Returns true if the consistent_reads block has + # a true state, false otherwise. + # @private + def self.consistent_reads_state + Thread.current['_simple_db_consistent_reads_'] + end + + end +end diff --git a/lib/aws/simple_db/attribute.rb b/lib/aws/simple_db/attribute.rb new file mode 100644 index 00000000000..782bad3c344 --- /dev/null +++ b/lib/aws/simple_db/attribute.rb @@ -0,0 +1,159 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/simple_db/consistent_read_option' +require 'aws/simple_db/put_attributes' +require 'aws/simple_db/delete_attributes' + +module AWS + class SimpleDB + + # Represents a single named item attribute in SimpleDB. + class Attribute + + include Model + include Enumerable + include ConsistentReadOption + include PutAttributes + include DeleteAttributes + + # @private + def initialize item, name, options = {} + @item = item + @name = name + super + end + + # @return [Item] The item this attribute belongs to. + attr_reader :item + + # @return [String] The name of this attribute. + attr_reader :name + + # Sets all values for this attribute, replacing current values. + # + # @example Setting a list of values + # attributes['colors'].set 'red', 'blue', 'green' + # + # @example Setting an array of values + # attributes['colors'].set ['red', 'blue'] + # + # @param *values An array or list of attribute values to set. + # @return [nil] + def set *values + put(values, true) + nil + end + + # Appends values to this attribute. Duplicate values are ignored + # by SimpleDB. + # + # @example Adding a list of values + # + # attributes['colors'].add 'red', 'blue', 'green' + # + # @example Adding an array of values + # + # attributes['colors'].add ['red', 'blue'] + # + # @param *values An array or list of attribute values to add. + # @return [nil] + def add *values + put(values, false) + nil + end + alias_method :<<, :add + + # Deletes this attribute or specific values from this attribute. + # + # @example Delete the attribute and all of its values + # + # item.attributes['color'].delete + # + # @example Delete specific attribute values + # + # item.attributes['color'].delete('red', 'blue') + # + # @param values One ore more values to remove from this attribute. + # If values is empty, then all attribute values are deleted + # (which deletes this attribute). + # @return [nil] + def delete *values + expect_opts = values.pop if values.last.kind_of?(Hash) + + if values.empty? + delete_named_attributes(name, expect_opts || {}) + else + delete_attribute_values(Hash[[[name, values]]]. + merge(expect_opts || {})) + end + nil + end + + # Yields once for each value on this attribute. + # + # @yield [attribute_value] Yields once for each domain in the account. + # @yieldparam [String] attribute_value + # @param [Hash] options + # @option options [Boolean] :consistent_read (false) A consistent read + # returns values that reflects all writes that received a successful + # response prior to the read. + # @return [nil] + def each options = {}, &block + + resp = client.get_attributes( + :domain_name => item.domain.name, + :item_name => item.name, + :attribute_names => [name], + :consistent_read => consistent_read(options)) + + resp.attributes.each do |attribute| + yield(attribute.value) + end + + nil + + end + + # Returns all values for this attribute as an array of strings. + # + # @example + # item.attributes['ratings'].values + # #=> ['5', '3', '4'] + # + # @param [Hash] options + # @option options [Boolean] :consistent_read (false) A consistent read + # returns values that reflects all writes that received a successful + # response prior to the read. + # @return [Array] An array of attribute values + def values options = {} + values = [] + self.each(options) do |value| + values << value + end + values + end + + # @private + protected + def put values, replace + expect_opts = values.pop if values.last.kind_of?(Hash) + do_put(attribute_hashes(Hash[[[name, values]]], + replace), + expect_opts || {}) + end + + end + end +end diff --git a/lib/aws/simple_db/attribute_collection.rb b/lib/aws/simple_db/attribute_collection.rb new file mode 100644 index 00000000000..cd612074ac6 --- /dev/null +++ b/lib/aws/simple_db/attribute_collection.rb @@ -0,0 +1,227 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/simple_db/attribute' +require 'aws/simple_db/consistent_read_option' +require 'aws/simple_db/put_attributes' +require 'aws/simple_db/delete_attributes' + +module AWS + class SimpleDB + class AttributeCollection + + include Model + include Enumerable + include ConsistentReadOption + include PutAttributes + include DeleteAttributes + + # @param [Item] The item to create an attribute collection for. + # @return [AttributeCollection] + def initialize item, options = {} + @item = item + super + end + + # @return [Item] The item this collection belongs to. + attr_reader :item + + # Retuns an Attribute with the given name. + # + # @note This does not make a request to SimpleDB. + # + # You can ask for any attribute by name. The attribute may or may not + # actually exist in SimpleDB. + # + # @example Get an attribute by symbol or string name + # colors = item.attributes[:colors] + # colors = item.attributes['colors'] + # @param [String, Symbol] attribute_name name of the attribute to get. + # @return [Item] An item with the given name. + def [] attribute_name + Attribute.new(item, attribute_name.to_s) + end + + # Sets the values for a given attribute. + # + # @example Replace all of the values for the named attribute. + # item.attributes[:color] = 'red', 'blue' + # @return This method returns the values passed to it. + def []= attribute_name, *values + self[attribute_name].set(*values) + end + + # Yields all attribute values with their names. + # + # @example Getting all values for an item + # + # item.attributes.each_value do |name, value| + # puts "#{name}: #{value}" + # end + # + # @yield [attribute_name, attribute_value] Yields once for every + # attribute value on the item. + # @yieldparam [String] attribute_name + # @yieldparam [String] attribute_value + # @param [Hash] options + # @option options [Boolean] :consistent_read (false) Causes this + # method to yield the most current attributes for this item. + # @return [nil] + def each_value options = {}, &block + + list = client.get_attributes( + :domain_name => item.domain.name, + :item_name => item.name, + :consistent_read => consistent_read(options)) + + list.attributes.each do |attribute| + attribute_name = attribute.name + attribute_value = attribute.value + yield(attribute_name, attribute_value) + end + + nil + + end + + # Yields all attribute for this item. + # + # @example Getting all attributes for an item + # + # item.attributes.each do |attribute| + # puts attribute.name + # end + # + # @yield [attribute] Yields once for every attribute + # on the item. Yields each attribute only one time, even it + # has multiple values. + # @yieldparam [Attribute] attribute + # @param [Hash] options + # @option options [Boolean] :consistent_read (false) Causes this + # method to yield the most current attributes for this item. + # @return [nil] + def each options = {}, &block + yielded = {} + each_value(options) do |attribute_name, attribute_value| + unless yielded[attribute_name] + attribute = self[attribute_name] + yield(attribute) + yielded[attribute_name] = true + end + end + nil + end + + # Replaces attributes for the {#item}. + # + # The +attributes_hash+ should have attribute names as keys. The + # hash values should be either strings or arrays of strings. + # + # Attributes not named in this hash are left alone. Attributes named + # in this hash are replaced. + # + # @example + # + # item.attributes.set( + # 'colors' => ['red', 'blue'], + # 'category' => 'clearance') + # + # @param [Hash] attributes + # @return [nil] + def replace attributes + do_put(attribute_hashes(attributes, true), attributes) + end + alias_method :set, :replace + + # Adds values to attributes on the {#item}. + # + # The +attributes_hash+ should have attribute names as keys. The + # hash values should be either strings or arrays of strings. + # + # @example + # + # item.attributes.add( + # 'colors' => ['red', 'blue'], + # 'category' => 'clearance') + # + # @param[Hash] attribute_hash + # @return [nil] + def add attributes + do_put(attribute_hashes(attributes, false), attributes) + end + + # Perform a mixed update of added and replace attribues. + # + # item.attributes.put( + # :add => { 'colors' => %w(green blue), 'tags' => 'cool' } + # :replace => { 'quantity' => 5 } + # ) + # + # @param [Hash] options + # @option options [Hash] :add A hash of attribute names and values to + # append to this item. + # @option options [Hash] :replace A hash of attribute names and values to + # add to this item. If there are currently attributes of the same + # name they will be replaced (not appended to). + # @option options [Hash] :replace + # @return [nil] + def put options = {} + add = options[:add] || {} + replace = options[:replace] || {} + attributes = attribute_hashes(add, false) + attributes += attribute_hashes(replace, true) + do_put(attributes, options) + end + + # Returns a hash of all attributes (names and values). + # The attribute names are strings and the values are + # arrays of strings. + # + # @example + # item.attributes.to_h + # #=> { 'colors' => ['red','blue'], 'size' => ['large'] } + # + # @param [Hash] options + # @option options [Boolean] :consistent_read (false) Causes this + # method to return the most current attributes values. + # @return [Hash] + def to_h options = {} + hash = {} + each_value(options) do |attribute_name,attribute_value| + hash[attribute_name] ||= [] + hash[attribute_name] << attribute_value + end + hash + end + + # Delete one or more attributes from {#item}. + # + # @example Delete a list of attributes by name + # item.attributes.delete 'size', 'color' + # item.attributes.delete %w(size color) + # + # @param attribute_names An array or list of attribute names to delete. + # @return [nil] + def delete *args + if args.size == 1 and args.first.kind_of?(Hash) + delete_attribute_values(args.first) + else + delete_named_attributes(*args) + end + nil + end + + end + end +end diff --git a/lib/aws/simple_db/client.rb b/lib/aws/simple_db/client.rb new file mode 100644 index 00000000000..2943fe085cf --- /dev/null +++ b/lib/aws/simple_db/client.rb @@ -0,0 +1,52 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/base_client' +require 'aws/configured_client_methods' +require 'aws/simple_db/request' +require 'aws/simple_db/client/xml' +require 'aws/simple_db/client/options' +require 'aws/inflection' +require 'aws/simple_db/errors' +require 'time' + +module AWS + class SimpleDB + + # @private + class Client < BaseClient + + include ConfiguredClientMethods + + API_VERSION = '2009-04-15' + + REGION_US_E1 = 'sdb.amazonaws.com' + REGION_US_W1 = 'sdb.us-west-1.amazonaws.com' + REGION_EU_W1 = 'sdb.eu-west-1.amazonaws.com' + REGION_APAC_SE1 = 'sdb.ap-southeast-1.amazonaws.com' + + REQUEST_CLASS = SimpleDB::Request + + configure_client + + def valid_domain_name? name + self.class.valid_domain_name?(name) + end + + def self.valid_domain_name? name + name.to_s =~ /^[a-z_\-\.]{3,255}$/i ? true : false + end + + end + end +end diff --git a/lib/aws/simple_db/client/options.rb b/lib/aws/simple_db/client/options.rb new file mode 100644 index 00000000000..d6a5b92f951 --- /dev/null +++ b/lib/aws/simple_db/client/options.rb @@ -0,0 +1,34 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/configured_option_grammars' + +module AWS + class SimpleDB + class Client < BaseClient + + # @private + module Options + + include ConfiguredOptionGrammars + + define_configured_grammars + + CustomizedListDomains = + ListDomains.customize("MaxNumberOfDomains" => + [{ :rename => "limit" }]) + + end + end + end +end diff --git a/lib/aws/simple_db/client/xml.rb b/lib/aws/simple_db/client/xml.rb new file mode 100644 index 00000000000..698ef502610 --- /dev/null +++ b/lib/aws/simple_db/client/xml.rb @@ -0,0 +1,68 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/configured_xml_grammars' +require 'aws/ignore_result_element' +require 'aws/xml_grammar' + +module AWS + class SimpleDB + class Client < BaseClient + + # @private + module XML + + include ConfiguredXmlGrammars + + extend IgnoreResultElement + + BaseResponse = XmlGrammar.customize do + element "ResponseMetadata" do + element "BoxUsage" do + float_value + end + end + end + + BaseError = XmlGrammar.customize do + element("Errors") do + ignore + element("Error") do + ignore + element("BoxUsage") { float_value } + end + end + end + + define_configured_grammars + + CustomizedSelect = Select.customize do + element "SelectResult" do + element "NextToken" do + force + end + end + end + + CustomizedListDomains = ListDomains.customize do + element "ListDomainsResult" do + element "NextToken" do + force + end + end + end + + end + end + end +end diff --git a/lib/aws/simple_db/consistent_read_option.rb b/lib/aws/simple_db/consistent_read_option.rb new file mode 100644 index 00000000000..7a28863cfd6 --- /dev/null +++ b/lib/aws/simple_db/consistent_read_option.rb @@ -0,0 +1,42 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + class SimpleDB + + # @private + module ConsistentReadOption + + # Determines if SimpleDB should be read consistently or not. + # + # Precedence is given to: + # + # * +:consistent_read+ option + # * SimpleDB.consistent_reads block value + # * AWS.config.simple_db_consistent_reads? + # + # @return [Boolean] Returns true if a read should be made consistently + # to SimpleDB. + def consistent_read options + if options.has_key?(:consistent_read) + options[:consistent_read] ? true : false + elsif SimpleDB.send(:in_consistent_reads_block?) + SimpleDB.send(:consistent_reads_state) + else + config.simple_db_consistent_reads? + end + end + + end + end +end diff --git a/lib/aws/simple_db/delete_attributes.rb b/lib/aws/simple_db/delete_attributes.rb new file mode 100644 index 00000000000..7dfbed49f1d --- /dev/null +++ b/lib/aws/simple_db/delete_attributes.rb @@ -0,0 +1,64 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/simple_db/expect_condition_option' + +module AWS + class SimpleDB + + # @private + module DeleteAttributes + + include ExpectConditionOption + + # @private + protected + def delete_named_attributes *attribute_names + expect_opts = attribute_names.pop if attribute_names.last.kind_of?(Hash) + attributes = attribute_names.flatten.collect{|n| { :name => n.to_s } } + opts = { + :domain_name => item.domain.name, + :item_name => item.name, + :attributes => attributes, + :expected => expect_condition_opts(expect_opts || {}) + } + opts.delete(:expected) if opts[:expected].empty? + client.delete_attributes(opts) unless attributes.empty? + end + + # @private + protected + def delete_attribute_values(attributes) + opts = { + :domain_name => item.domain.name, + :item_name => item.name, + :attributes => [], + :expected => expect_condition_opts(attributes) + } + attributes.each do |name, values| + if name != :"if" && name != :"unless" + [values].flatten.each do |value| + attribute_value = { :name => name.to_s } + attribute_value[:value] = value unless value == :all + opts[:attributes] << attribute_value + end + end + end + opts.delete(:expected) if opts[:expected].empty? + client.delete_attributes(opts) + end + + end + + end +end diff --git a/lib/aws/simple_db/domain.rb b/lib/aws/simple_db/domain.rb new file mode 100644 index 00000000000..9607a64ef39 --- /dev/null +++ b/lib/aws/simple_db/domain.rb @@ -0,0 +1,118 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/errors' +require 'aws/model' +require 'aws/simple_db/domain_metadata' +require 'aws/simple_db/item_collection' + +module AWS + class SimpleDB + + # Represents a domain in SimpleDB. + # + # Domains, like database tables, must exist before you can write to one. + # + # @example Creating a domain + # domain = SimpleDB.new.domains.create('mydomain') + # + # @example Getting a domain + # domain = SimpleDB.new.domains['mydomain'] + # + # @see DomainCollection + # + class Domain + + # @private + class NonEmptyDeleteError < StandardError; end + + include Model + + # @param [String] The name of a SimpleDB domain to reference. + def initialize(name, options = {}) + super(options) + @name = name + end + + # Returns the name for this domain. + # + # @return [String] The name of this domain. + attr_reader :name + + # Returns true if the domain has no items, false otherwise. + # + # @return [Boolean] Returns true if the domain has no items. + def empty? + metadata.item_count == 0 + end + + # Deletes the (empty) domain. + # + # @note If you need to delete a domain with items, call {#delete!} + # @raise [NonEmptyDeleteError] Raises if the domain is not empty. + # @return [nil] + def delete + unless empty? + raise NonEmptyDeleteError, "delete called without :force " + + "on a non-empty domain" + end + client.delete_domain(:domain_name => name) + nil + end + + # Deletes the domain and all of its items. + # + # @return [nil] + def delete! + client.delete_domain(:domain_name => name) + nil + end + + # Returns true if this domain exists, false otherwise. + # + # @return [Boolean] Returns true if the domain exists. + def exists? + begin + client.domain_metadata(:domain_name => name) + true + rescue Errors::NoSuchDomain + false + end + end + + # Returns a metadata object that can provide information about + # this domain. + # + # @return [DomainMetadata] + def metadata + DomainMetadata.new(self) + end + + # Returns a collection that represents all of the items in this domain. + # + # @return [ItemCollection] + def items + ItemCollection.new(self) + end + + # An irb-friendly string representation of this object. + # + # @return [String] + # @private + def inspect + "#<#{self.class}:#{name}>" + end + + end + end +end diff --git a/lib/aws/simple_db/domain_collection.rb b/lib/aws/simple_db/domain_collection.rb new file mode 100644 index 00000000000..3dd41057cd6 --- /dev/null +++ b/lib/aws/simple_db/domain_collection.rb @@ -0,0 +1,116 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/simple_db/domain' + +module AWS + class SimpleDB + + # An Enumerable collection representing all your domains in SimpleDB. + # + # Use a DomainCollection to create, get and list domains. + # + # @example Creating a domain in SimpleDB + # + # sdb = SimpleDB.new + # domain = sdb.domains.create('mydomain') + # + # @example Getting a domain with indifferent access + # + # domain = sdb.domains[:mydomain] + # domain = sdb.domains['mydomain'] + # + # @example Enumerating domains + # + # sdb.domains.each do |domain| + # puts domain.name + # end + # + class DomainCollection + + include Model + include Enumerable + + # Creates a domain in SimpleDB and returns a domain object. + # + # @note This operation might take 10 or more seconds to complete. + # @note Creating a domain in SimpleDB is an idempotent operation; + # running it multiple times using the same domain name will not + # result in an error. + # @note You can create up to 100 domains per account. + # @param [String] domain_name + # @return [Domain] Returns a new domain with the given name. + def create(domain_name) + client.create_domain(:domain_name => domain_name) + domain_named(domain_name) + end + + # Returns a domain object with the given name. + # + # @note This does not attempt to create the domain if it does not + # already exist in SimpleDB. Use {#create} to add a domain to SDB. + # + # @param [String] domain_name The name of the domain to return. + # @return [Domain] Returns the domain with the given name. + def [] domain_name + domain_named(domain_name) + end + + # @note Normally your account has a limit of 100 SimpleDB domains. You can {request more here}[http://aws.amazon.com/contact-us/simpledb-limit-request/] + # @yield [domain] Yields once for every domain in your account. + # @yieldparam [Domain] domain + # @param [Hash] options + # @option options [Integer] :limit (nil) The maximum number of + # domains to yield. + # @option options [Integer] :batch_size (100) The number of domains to + # fetch each request to SimpleDB. Maximum is 100. + # @return [nil] + def each options = {}, &block + + total_limit = options[:limit] + batch_size = options[:batch_size] || 100 + received = 0 + next_token = nil + + begin + + limit = total_limit ? + [total_limit - received, batch_size].min : + batch_size + + list_options = { :limit => limit } + list_options[:next_token] = next_token if next_token + list = client.list_domains(list_options) + + next_token = list.next_token + received += list.domain_names.length + + list.domain_names.each do |name| + yield(domain_named(name)) + end + + end while next_token and (total_limit.nil? or received < total_limit) + nil + end + + # @return [Domain] Returns a domain with the given name. + # @private + protected + def domain_named name + Domain.new(name.to_s, :config => config) + end + + end + end +end diff --git a/lib/aws/simple_db/domain_metadata.rb b/lib/aws/simple_db/domain_metadata.rb new file mode 100644 index 00000000000..18325603641 --- /dev/null +++ b/lib/aws/simple_db/domain_metadata.rb @@ -0,0 +1,112 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' + +module AWS + class SimpleDB + + # SimpleDB can report on the amount of data stored in a domain, + # the number of items, etc. + # + # @example + # + # sdb = SimpleDB.new + # sdb.domains['mydomain'].metadata.to_h + # + # # the hash returned above might look like: + # { + # :timestamp => 1300841880, + # :attribute_names_size_bytes => 12, + # :item_count => 1, + # :attribute_value_count => 6, + # :attribute_values_size_bytes => 25, + # :item_names_size_bytes => 3, + # :attribute_name_count => 3 + # } + # + class DomainMetadata + + include Model + + # @private + ATTRIBUTES = [ + :item_count, + :item_names_size_bytes, + :attribute_name_count, + :attribute_names_size_bytes, + :attribute_value_count, + :attribute_values_size_bytes, + :timestamp, + ] + + # @param [Domain] The domain to fetch metadata for. + # @return [DomainMetadata] + def initialize domain, options = {} + @domain = domain + super + end + + # @return [Domain] The domain this metadata is describing. + attr_reader :domain + + # @return [Integer] The number of all items in the {#domain}. + def item_count + self.to_h[:item_count] + end + + # @return [Integer] The total size of all item names in the {#domain}, + # in bytes. + def item_names_size_bytes + self.to_h[:item_names_size_bytes] + end + + # @return [Integer] The number of unique attribute names in the + # {#domain}. + def attribute_name_count + self.to_h[:attribute_name_count] + end + + # @return [Integer] The total size of all unique attribute names, + # in bytes. + def attribute_names_size_bytes + self.to_h[:attribute_names_size_bytes] + end + + # @return [Integer] The number of all attribute name/value pairs in + # the {#domain}. + def attribute_value_count + self.to_h[:attribute_value_count] + end + + # @return [Integer] The total size of all attribute values, in bytes. + def attribute_values_size_bytes + self.to_h[:attribute_values_size_bytes] + end + + # @return [Integer] The data and time when metadata was calculated + # in Epoch (UNIX) time. + def timestamp + self.to_h[:timestamp] + end + + # @return [Hash] A hash of all of the metadata attributes for + # this {#domain}. + def to_h + meta = client.domain_metadata(:domain_name => domain.name) + ATTRIBUTES.inject({}) {|h,attr| h[attr] = meta.send(attr); h } + end + + end + end +end diff --git a/lib/aws/simple_db/errors.rb b/lib/aws/simple_db/errors.rb new file mode 100644 index 00000000000..d9b55543c70 --- /dev/null +++ b/lib/aws/simple_db/errors.rb @@ -0,0 +1,46 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/lazy_error_classes' +require 'aws/simple_db/client/xml' + +module AWS + class SimpleDB + + # This module contains exception classes for each of the error + # types that SimpleDB can return. You can use these classes to + # rescue specific errors, for example: + # + # begin + # SimpleDB.new.domains.mydomain. + # items["foo"].attributes.set(:color => "red") + # rescue SimpleDB::Errors::NoSuchDomain => e + # SimpleDB.new.domians.create("mydomain") + # retry + # end + # + # Each exception has: + # + # * +code+: returns the error code as a string. + # * +box_usage+: returns the box usage for the operation. + # + # All errors raised as a result of error responses from the + # service are instances of either {ClientError} or {ServerError}. + # @private + module Errors + BASE_ERROR_GRAMMAR = Client::XML::BaseError + include LazyErrorClasses + end + + end +end diff --git a/lib/aws/simple_db/expect_condition_option.rb b/lib/aws/simple_db/expect_condition_option.rb new file mode 100644 index 00000000000..12e52165609 --- /dev/null +++ b/lib/aws/simple_db/expect_condition_option.rb @@ -0,0 +1,45 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + class SimpleDB + + # @private + module ExpectConditionOption + + # @private + protected + def expect_condition_opts(opts) + expected = [] + opts.each do |name, value| + case name + when :if + (expected_name, expected_value) = value.to_a.first + expected << { + :name => expected_name.to_s, + :value => expected_value + } + when :unless + expected << { + :name => value.to_s, + :exists => false + } + end + end + expected + end + + end + + end +end diff --git a/lib/aws/simple_db/item.rb b/lib/aws/simple_db/item.rb new file mode 100644 index 00000000000..2072cbd8cdd --- /dev/null +++ b/lib/aws/simple_db/item.rb @@ -0,0 +1,84 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/simple_db/item_data' +require 'aws/simple_db/attribute_collection' +require 'aws/simple_db/expect_condition_option' +require 'aws/simple_db/consistent_read_option' + +module AWS + class SimpleDB + + # Represents a single item in a SimpleDB domain. You can use + # this class to delete the item or get its data. You can also + # use it to access the {AttributeCollection} for the item in + # order to add, remove, or read the item's attributes. + # + # item = AWS::SimpleDB.new.domains['mydomain'].items['item-id'] + # + class Item + + include Model + include ExpectConditionOption + include ConsistentReadOption + + # @param [Domain] domain The domain the item belongs to + # @param [String] name The name of the item in SimpleDB. + # @param [Hash] options + def initialize domain, name, options = {} + @domain = domain + @name = name + super + end + + # @return [Domain] The domain this item belongs to. + attr_reader :domain + + # @return [String] The item name. + attr_reader :name + + # @return [AttributeCollection] A collection representing all attributes + # for this item. + def attributes + AttributeCollection.new(self) + end + + # Deletes the item and all of its attributes from SimpleDB. + # @return [nil] + def delete options = {} + delete_opts = {} + delete_opts[:domain_name] = domain.name + delete_opts[:item_name] = name + delete_opts[:expected] = expect_condition_opts(options) + delete_opts.delete(:expected) if delete_opts[:expected].empty? + client.delete_attributes(delete_opts) + nil + end + + # Returns all of the item's attributes in an {ItemData} instance. + # @return [ItemData] An object with all of the loaded attribute names + # and values for this item. + def data options = {} + get_opts = {} + get_opts[:domain_name] = domain.name + get_opts[:item_name] = name + get_opts[:consistent_read] = consistent_read(options) + r = client.get_attributes(get_opts) + ItemData.new(:name => name, :domain => domain, :response_object => r) + end + + end + + end +end diff --git a/lib/aws/simple_db/item_collection.rb b/lib/aws/simple_db/item_collection.rb new file mode 100644 index 00000000000..777e6978ad9 --- /dev/null +++ b/lib/aws/simple_db/item_collection.rb @@ -0,0 +1,594 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/simple_db/item' +require 'aws/simple_db/item_data' +require 'aws/simple_db/consistent_read_option' + +# for 1.8.6 +require 'enumerator' + +module AWS + class SimpleDB + + # Represents a collection of items in a SimpleDB domain. + class ItemCollection + + include Model + include Enumerable + include ConsistentReadOption + + # @private + attr_reader :conditions + + # @private + attr_reader :sort_instructions + + # @param [Domain] domain The domain that you want an item collection for. + # @return [ItemCollection] + def initialize domain, options = {} + @domain = domain + @conditions = [] + @conditions += options[:conditions] if options[:conditions] + @sort_instructions = options[:sort_instructions] if options[:sort_instructions] + @not_null_attribute = options[:not_null_attribute] + @limit = options[:limit] if options[:limit] + super + end + + # @return [Domain] The domain the items belong to. + attr_reader :domain + + # Creates a new item in SimpleDB with the given attributes: + # + # domain.items.create('shirt', { + # 'colors' => ['red', 'blue'], + # 'category' => 'clearance'}) + # + # @overload create(item_name, attribute_hash) + # @param [String] item_name The name of the item as you want it stored + # in SimpleDB. + # @param [Hash] attribute_hash A hash of attribute names and values + # you want to store in SimpleDB. + # @return [Item] Returns a reference to the object that was created. + def create item_name, *args + item = self[item_name] + item.attributes.replace(*args) + item + end + + # Retuns an item with the given name. + # + # @note This does not make a request to SimpleDB. + # + # You can ask for any item. The named item may or may not actually + # exist in SimpleDB. + # + # @example Get an item by symbol or string name + # + # item = domain.items[:itemname] + # item = domain.items['itemname'] + # + # @param [String, Symbol] item_name name of the item to get. + # @return [Item] Returns an item with the given name. + def [] item_name + Item.new(domain, item_name.to_s) + end + + # Yields to the block once for each item in the domain. + # + # @example using each to fetch every item in the domain. + # + # domain.items.each do |item| + # puts item.name + # end + # + # @yield [item] Yields once for every item in the {#domain}. + # @yieldparam [Item] item + # @param options (see #select) + # @option options (see #select) + # @option options [Symbol or Array] :select Causes this method + # to behave like {#select} and yield {ItemData} instead of + # {Item} instances. + # @option options :batch_size Specifies a maximum number of records + # to fetch from SimpleDB in a single request. SimpleDB may return + # fewer items than :batch_size per request, but never more. + # @return [nil] + def each options = {}, &block + + return if handle_query_options(:each, options, &block) + + if attributes = options.delete(:select) + return select(attributes, options, &block) + end + + perform_select(options) do |response| + response.items.each do |item| + yield(self[item.name]) + end + end + + end + + # Retrieves data from each item in the domain. + # + # domain.items.select('size', 'color') + # + # You may optionally filter by a set of conditions. For example, + # to retrieve the attributes of each of the top 100 items in order of + # descending popularity as an array of hashes, you could do: + # + # items.order(:popularity, :desc).limit(100).select do |data| + # puts data.to_yaml + # end + # + # You can select specific attributes; for example, to get + # all the unique colors in the collection you could do: + # + # colors = Set.new + # items.select(:color) {|data| colors += data.attributes["color"] } + # + # Finally, you can specify conditions, sort instructions, and + # a limit in the same method call: + # + # items.select(:color, + # :where => "rating > 4", + # :order => [:popularity, :desc], + # :limit => 100) do |data| + # puts "Data for #{data.name}: #{data.attributes.inspect}" + # end + # + # @overload select(*attribute_names, options = {}) &block + # @param *attributes [Symbol, String, or Array] + # The attributes to retrieve. This can be: + # + # * +:all+ to retrieve all attributes (the default). + # * a Symbol or String to retrieve a single attribute. + # * an array of Symbols or Strings to retrieve multiple attributes. + # + # For single attributes or arrays of attributes, the + # attribute name may contain any characters that are valid + # in a SimpleDB attribute name; this method will handle + # escaping them for inclusion in the query. Note that you + # cannot use this method to select the number of items; use + # {#count} instead. + # + # @param [Hash] options Options for querying the domain. + # @option options [Boolean] :consistent_read (false) Causes this + # method to yield the most current data in the domain. + # @option options :where Restricts the item collection using + # {#where} before querying. + # @option options :order Changes the order in which the items + # will be yielded (see {#order}). + # @option options :limit [Integer] The maximum number of + # items to fetch from SimpleDB. More than one request may be + # required to satisfy the limit. + # @option options :batch_size Specifies a maximum number of records + # to fetch from SimpleDB in a single request. SimpleDB may return + # fewer items than :batch_size per request, but never more. + # @return If no block is given, an enumerator is returned. If a block + # was passed then nil is returned. + def select *attributes, &block + + options = attributes.last.is_a?(Hash) ? attributes.pop : {} + + args = attributes + [options] + + return if handle_query_options(:select, *args, &block) + + unless block_given? + return Enumerator.new(self, :select, *args) + end + + if attributes.empty? + output_list = '*' + #elsif attributes == ['*'] + # output_list = '*' + else + output_list = [attributes].flatten.collect do |attr| + coerce_attribute(attr) + end.join(', ') + end + + perform_select(options.merge(:output_list => output_list)) do |response| + response.items.each do |item| + yield(ItemData.new(:domain => domain, :response_object => item)) + end + end + + nil + + end + + # Counts the items in the collection. + # + # domain.items.count + # + # You can use this method to get the total number of items in + # the domain, or you can use it with {#where} to count a subset + # of items. For example, to count the items where the "color" + # attribute is "red": + # + # domain.items.where("color" => "red").count + # + # You can also limit the number of items searched using the + # {#limit} method. For example, to count the number of items up + # to 500: + # + # domain.items.limit(500).count + # + # @param [Hash] options Options for counting items. + # + # @option options [Boolean] :consistent_read (false) Causes this + # method to yield the most current data in the domain. + # @option options :where Restricts the item collection using + # {#where} before querying. + # @option options :limit [Integer] The maximum number of + # items to fetch from SimpleDB. More than one request may be + # required to satisfy the limit. + def count options = {}, &block + return if handle_query_options(:count, options, &block) + + options = options.merge(:output_list => "count(*)") + + count = 0 + next_token = nil + + while limit.nil? || count < limit and + response = select_request(options, next_token) + + if domain_item = response.items.first and + count_attribute = domain_item.attributes.first + count += count_attribute.value.to_i + end + + next_token = response.next_token + break unless next_token + + end + + count + end + alias_method :size, :count + + # Identifies quoted regions in the string, giving access to + # the regions before and after each quoted region, for example: + # "? ? `foo?``bar?` ? 'foo?' ?".scan(OUTSIDE_QUOTES_REGEX) + # # => [["? ? ", "`foo?``bar?`", " ? "], ["", "'foo?'", " ?"]] + OUTSIDE_QUOTES_REGEX = Regexp.compile('([^\'"`]*)(`(?:[^`]*(?:``))*[^`]*`|'+ + '\'(?:[^\']*(?:\'\'))*[^\']*\'|'+ + '"(?:[^"]*(?:""))*[^"]*")([^\'`"]*)') + + # Returns an item collection defined by the given conditions + # in addition to any conditions defined on this collection. + # For example: + # + # items = domain.items.where(:color => 'blue'). + # where('engine_type is not null') + # + # # does SELECT itemName() FROM `mydomain` + # # WHERE color = "blue" AND engine_type is not null + # items.each { |i| ... } + # + # == Hash Conditions + # + # When +conditions+ is a hash, each entry produces a condition + # on the attribute named in the hash key. For example: + # + # # produces "WHERE `foo` = 'bar'" + # domain.items.where(:foo => 'bar') + # + # You can pass an array value to use an "IN" operator instead + # of "=": + # + # # produces "WHERE `foo` IN ('bar', 'baz')" + # domain.items.where(:foo => ['bar', 'baz']) + # + # You can also pass a range value to use a "BETWEEN" operator: + # + # # produces "WHERE `foo` BETWEEN 'bar' AND 'baz' + # domain.items.where(:foo => 'bar'..'baz') + # + # # produces "WHERE (`foo` >= 'bar' AND `foo` < 'baz')" + # domain.items.where(:foo => 'bar'...'baz') + # + # == Placeholders + # + # If +conditions+ is a string and "?" appears outside of any + # quoted part of the expression, +placeholers+ is expected to + # contain a value for each of the "?" characters in the + # expression. For example: + # + # # produces "WHERE foo like 'fred''s % value'" + # domain.items.where("foo like ?", "fred's % value") + # + # Array values are surrounded with parentheses when they are + # substituted for a placeholder: + # + # # produces "WHERE foo in ('1', '2')" + # domain.items.where("foo in ?", [1, 2]) + # + # Note that no substitutions are made within a quoted region + # of the query: + # + # # produces "WHERE `foo?` = 'red'" + # domain.items.where("`foo?` = ?", "red") + # + # # produces "WHERE foo = 'fuzz?' AND bar = 'zap'" + # domain.items.where("foo = 'fuzz?' AND bar = ?", "zap") + # + # Also note that no attempt is made to correct for syntax: + # + # # produces "WHERE 'foo' = 'bar'", which is invalid + # domain.items.where("? = 'bar'", "foo") + # + # @return [ItemCollection] Returns a new item collection with the + # additional conditions. + def where(conditions, *substitutions) + case conditions + when String + conditions = [replace_placeholders(conditions, *substitutions)] + when Hash + conditions = conditions.map do |name, value| + name = coerce_attribute(name) + case value + when Array + "#{name} IN " + coerce_substitution(value) + when Range + if value.exclude_end? + "(#{name} >= #{coerce_substitution(value.begin)} AND " + + "#{name} < #{coerce_substitution(value.end)})" + else + "#{name} BETWEEN #{coerce_substitution(value.begin)} AND " + + coerce_substitution(value.end) + end + else + "#{name} = " + coerce_substitution(value) + end + end + end + + collection_with(:conditions => self.conditions + conditions) + end + + # Changes the order in which results are returned or yielded. + # For example, to get item names in descending order of + # popularity, you can do: + # + # domain.items.order(:popularity, :desc).map(&:name) + # + # @param attribute [String or Symbol] The attribute name to + # order by. + # @param order [String or Symbol] The desired order, which may be: + # * +asc+ or +ascending+ (the default) + # * +desc+ or +descending+ + # @return [ItemCollection] Returns a new item collection with the + # given ordering logic. + def order(attribute, order = nil) + sort = coerce_attribute(attribute) + sort += " DESC" if order.to_s =~ /^desc(ending)?$/ + sort += " ASC" if order.to_s =~ /^asc(ending)?$/ + collection_with(:sort_instructions => sort, + :not_null_attribute => attribute.to_s) + end + + # Limits the number of items that are returned or yielded. + # For example, to get the 100 most popular item names: + # + # domain.items. + # order(:popularity, :desc). + # limit(100). + # map(&:name) + # + # @overload limit + # @return [Integer] Returns the current limit for the collection. + # @overload limit(value) + # @return [ItemCollection] Returns a collection with the given limit. + def limit(*args) + return @limit if args.empty? + collection_with(:limit => Integer(args.first)) + end + + # turns e.g. each(:where => 'foo', ...) into where('foo').each(...) + # @private + protected + def handle_query_options(method, *args, &block) + options = args.pop if args.last.kind_of?(Hash) + if query_option = (options.keys & [:where, :order, :limit]).first + option_args = options[query_option] + option_args = [option_args] unless option_args.kind_of?(Array) + options.delete(query_option) + send(query_option, *option_args). + send(method, *(args + [options]), &block) + true + else + false + end + end + + # @private + protected + def perform_select(options = {}) + + next_token = options[:next_token] + batch_size = options[:batch_size] ? Integer(options[:batch_size]) : nil + total = 0 + + begin + + # if the user provided a batch size we need to rewrite the + # select expression's LIMIT clause. + if batch_size + max = limit ? [limit - total, batch_size].min : batch_size + else + max = nil + end + + response = select_request(options, next_token, max) + + yield(response) + + next_token = response.next_token + + total += response.items.size + + end while next_token && (limit.nil? || total < limit) + end + + protected + def select_request(options, next_token = nil, limit = nil) + opts = {} + opts[:select_expression] = select_expression(options[:output_list]) + opts[:consistent_read] = consistent_read(options) + opts[:next_token] = next_token if next_token + + if limit + unless opts[:select_expression].gsub!(/LIMIT \d+/, "LIMIT #{limit}") + opts[:select_expression] << " LIMIT #{limit}" + end + end + + client.select(opts) + end + + # @private + protected + def select_expression(output_list = nil) + output_list ||= "itemName()" + "SELECT #{output_list} FROM `#{domain.name}`" + + where_clause + order_by_clause + limit_clause + end + + # @private + protected + def limit_clause + if limit + " LIMIT #{limit}" + else + "" + end + end + + # @private + protected + def where_clause + all_conditions = conditions.dup + if @not_null_attribute + all_conditions << coerce_attribute(@not_null_attribute) + " IS NOT NULL" + end + if all_conditions.empty? + "" + else + " WHERE " + all_conditions.join(" AND ") + end + end + + # @private + protected + def order_by_clause + if sort_instructions + " ORDER BY " + sort_instructions + else + "" + end + end + + # @private + protected + def collection_with(opts) + ItemCollection.new(domain, { + :limit => limit, + :conditions => conditions, + :sort_instructions => sort_instructions }.merge(opts)) + end + + # @private + protected + def replace_placeholders(str, *substitutions) + named = {} + named = substitutions.pop if substitutions.last.kind_of?(Hash) + if str =~ /['"`]/ + count = 0 + str = str.scan(OUTSIDE_QUOTES_REGEX). + map do |(before, quoted, after)| + + (before, after) = [before, after].map do |s| + s, count = + replace_placeholders_outside_quotes(s, count, substitutions, named) + s + end + [before, quoted, after].join + end.join + else + # no quotes + str, count = + replace_placeholders_outside_quotes(str, 0, substitutions, named) + end + raise ArgumentError.new("extra value(s): #{substitutions.inspect}") unless + substitutions.empty? + str + end + + # @private + protected + def replace_placeholders_outside_quotes(str, count, substitutions, named = {}) + str, count = replace_positional_placeders(str, count, substitutions) + str = replace_named_placeholders(str, named) + [str, count] + end + + # @private + protected + def replace_positional_placeders(str, count, substitutions) + str = str.gsub("?") do |placeholder| + count += 1 + raise ArgumentError.new("missing value for placeholder #{count}") if + substitutions.empty? + coerce_substitution(substitutions.shift) + end + [str, count] + end + + # @private + protected + def replace_named_placeholders(str, named) + named.each do |name, value| + str = str.gsub(name.to_sym.inspect, coerce_substitution(value)) + end + str.scan(/:\S+/) do |missing| + raise ArgumentError.new("missing value for placeholder #{missing}") + end + str + end + + # @private + protected + def coerce_substitution(subst) + if subst.kind_of?(Array) + "(" + + subst.flatten.map { |s| coerce_substitution(s) }.join(", ") + ")" + else + "'" + subst.to_s.gsub("'", "''") + "'" + end + end + + # @private + protected + def coerce_attribute(name) + '`' + name.to_s.gsub('`', '``') + '`' + end + + end + end +end diff --git a/lib/aws/simple_db/item_data.rb b/lib/aws/simple_db/item_data.rb new file mode 100644 index 00000000000..2bf2758e808 --- /dev/null +++ b/lib/aws/simple_db/item_data.rb @@ -0,0 +1,70 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module AWS + class SimpleDB + + # Holds the data for a SimpleDB item. While {Item} only proxies + # requests to return data, this class actually stores data + # returned by a query. For example, you can use this to get the + # list of items whose titles are palindromes using only a single + # request to SimpleDB (not counting pagination): + # + # items.enum_for(:select). + # select { |data| data.title == data.title.to_s.reverse }. + # map { |data| data.item } + # + # The {ItemCollection#select} call yields instances of ItemData, + # and the +map+ call in the example above gets the list of + # corresponding {Item} instances. + class ItemData + + # @private + def initialize(opts = {}) + @name = opts[:name] + @attributes = opts[:attributes] + @domain = opts[:domain] + + if obj = opts[:response_object] + @name ||= obj.name if obj.respond_to?(:name) + if obj.respond_to?(:attributes) + @attributes ||= obj.attributes.inject({}) do |m, att| + m[att.name] ||= [] + m[att.name] << att.value + m + end + end + end + end + + # @return [String] The item name. + attr_reader :name + + # @return [Hash] A hash of attribute names to arrays of values. + attr_reader :attributes + + # @return [Domain] The domain from which the item data was retrieved. + attr_reader :domain + + # Returns the {Item} corresponding to this ItemData; you can + # use this to perform further operations on the item, or to + # fetch its most recent data. + # @return [Item] The item this data belongs to. + def item + domain[name] + end + + end + + end +end diff --git a/lib/aws/simple_db/put_attributes.rb b/lib/aws/simple_db/put_attributes.rb new file mode 100644 index 00000000000..46c19f6ba88 --- /dev/null +++ b/lib/aws/simple_db/put_attributes.rb @@ -0,0 +1,62 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/simple_db/expect_condition_option' + +module AWS + class SimpleDB + + # @private + module PutAttributes + + include ExpectConditionOption + + # Given a single hash of attribute names to values, returns a list + # of hashes suitable for the put_attributes :attributes option. + # @private + protected + def attribute_hashes attributes, replace + attribute_hashes = [] + attributes.each_pair do |attribute_name,values| + [values].flatten.each do |value| + attribute_hashes << { + :name => attribute_name.to_s, + :value => value.to_s, + :replace => replace, + } unless [:if, :unless].include?(attribute_name) + end + end + attribute_hashes + end + + # @private + protected + def do_put attribute_hashes, expect_opts = {} + return nil if attribute_hashes.empty? + + opts = { + :domain_name => item.domain.name, + :item_name => item.name, + :attributes => attribute_hashes, + :expected => expect_condition_opts(expect_opts) + } + opts.delete(:expected) if opts[:expected].empty? + + client.put_attributes(opts) + nil + end + + end + + end +end diff --git a/lib/aws/simple_db/request.rb b/lib/aws/simple_db/request.rb new file mode 100644 index 00000000000..cf326a565a9 --- /dev/null +++ b/lib/aws/simple_db/request.rb @@ -0,0 +1,27 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/http/request' +require 'aws/authorize_v2' + +module AWS + class SimpleDB + + # @private + class Request < AWS::Http::Request + + include AuthorizeV2 + + end + end +end diff --git a/lib/aws/simple_email_service.rb b/lib/aws/simple_email_service.rb new file mode 100644 index 00000000000..1511ca431fe --- /dev/null +++ b/lib/aws/simple_email_service.rb @@ -0,0 +1,373 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/common' +require 'aws/service_interface' +require 'aws/simple_email_service/client' +require 'aws/simple_email_service/email_address_collection' +require 'aws/simple_email_service/quotas' + +module AWS + + # This class is the starting point for working with Amazon + # SimpleEmailService (SES). + # + # To use Amazon SimpleEmailService you must first + # {sign up here}[http://aws.amazon.com/ses/] + # + # For more information about Amazon SimpleEmailService: + # + # * {Amazon SimpleEmailService}[http://aws.amazon.com/ses/] + # * {Amazon SimpleEmailService Documentation}[http://aws.amazon.com/documentation/ses/] + # + # = Credentials + # + # You can setup default credentials for all AWS services via + # AWS.config: + # + # AWS.config( + # :access_key_id => 'YOUR_ACCESS_KEY_ID', + # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') + # + # Or you can set them directly on the SimpleEmailService interface: + # + # ses = AWS::SimpleEmailService.new( + # :access_key_id => 'YOUR_ACCESS_KEY_ID', + # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') + # + # = Rails + # + # If you want to use Amazon SimpleEmailService to send email from your + # Rails application you just need to do 2 things: + # + # 1. Configure your AWS credentials with {AWS.config} + # 2. Set SES as the delivery method: + # + # config.action_mailer.delivery_method = :amazon_ses + # + # This has only been tested with Rails 2.3 and Rails 3.0. + # + # = Email Addresses + # + # Until you have {requested production access}[http://docs.amazonwebservices.com/ses/latest/DeveloperGuide/InitialSetup.Customer.html] + # you will only be able to send emails to and from verified email addresses. + # To verify an email address: + # + # ses.email_addresses.verify('youremailaddress@domain.com') + # + # AWS will send an email to the given email address. Follow the link in the + # email to verify the address. + # + # To explore the email addresses you have authorized: + # + # ses.email_addresses.each do |address| + # puts address + # end + # + # See {EmailAddressCollection} for more information on working with SES email + # addresses. + # + # = Sending Email + # + # To send a basic email you can use {#send_email}. + # + # ses.send_email( + # :subject => 'A Sample Email', + # :from => 'sender@domain.com', + # :to => 'receipient@domain.com', + # :body_text => 'Sample email text.', + # :body_html => '

Sample Email

') + # + # If you need to send email with attachments or have other special needs + # that send_email does not support you can use {#send_raw_email}. + # + # ses.send_raw_email(< 'to@foo.com', :from => 'from@foo.com') + # Subject: A Sample Email + # + # Sample email text. + # EMAIL + # + # = Quotas + # + # Based on several factors, Amazon SES determines how much email you can + # send and how quickly you can send it. These sending limits are defined + # as follows: + # + # * +:max_send_rate+ — Maximum number of emails you can send per second. + # * +:max_24_hour_send+ - Maximum number of emails you can send in a + # 24-hour period. + # + # To get your current quotas (and how many emails you have sent in the last + # 24 hours): + # + # ses.quotas + # # => {:max_24_hour_send=>200, :max_send_rate=>1.0, :sent_last_24_hours=>22} + # + # = Statistics + # + # You can get statistics about individual emails: + # + # ses.statistics.each do |stats| + # puts "Sent: #{stats[:sent]}" + # puts "Delivery Attempts: #{stats[:delivery_attempts]}" + # puts "Rejects: #{stats[:rejects]}" + # puts "Bounces: #{stats[:bounces]}" + # puts "Complaints: #{stats[:complaints]}" + # end + # + class SimpleEmailService + + include ServiceInterface + + # @return [EmailAddressCollection] Returns a collection that represents + # all of the verified email addresses for your account. + def email_addresses + EmailAddressCollection.new(:config => config) + end + + # Sends an email. + # + # ses.send_email( + # :subject => 'A Sample Email', + # :to => 'john@doe.com', + # :from => 'no@reply.com', + # :body_text => 'sample text ...', + # :body_html => '

sample text ...

') + # + # You can also pass multiple email addresses for the +:to+, +:cc+, + # +:bcc+ and +:reply_to+ options. Email addresses can also be + # formatted with names. + # + # ses.send_email( + # :subject => 'A Sample Email', + # :to => ['"John Doe" ', '"Jane Doe" '], + # :from => 'no@reply.com', + # :body_text => 'sample text ...') + # + # @param [Hash] options + # @option options [required,String] :subject The subject of the message. + # A short summary of the content, which will appear in the # + # recipient's inbox. + # @option options [required,String] :from The sender's email address. + # @option options [String,Array] :to The address(es) to send the email to. + # @option options [String,Array] :cc The address(es) to cc (carbon copy) + # the email to. + # @option options [String,Array] :bcc The address(es) to bcc (blind + # carbon copy) the email to. + # @option options [String,Array] :reply_to The reply-to email address(es) + # for the message. If the recipient replies to the message, each + # reply-to address will receive the reply. + # @option options [String] :return_path The email address to which + # bounce notifications are to be forwarded. If the message cannot be + # delivered to the recipient, then an error message will be returned + # from the recipient's ISP; this message will then be forwarded to + # the email address specified by the +:return_path+ option. + # @option options [String] :body_text The email text contents. + # You must provide +:body_text+, +:body_html+ or both. + # @option options [String] :body_html The email html contents. + # You must provide +:body_text+, +:body_html+ or both. + # @option options [String] :subject_charset The character set of the + # +:subject+ string. If the text must contain any other characters, + # then you must also specify the character set. Examples include + # UTF-8, ISO-8859-1, and Shift_JIS. Defaults to 7-bit ASCII. + # @option options [String] :body_text_charset The character set of the + # +:body_text+ string. If the text must contain any other characters, + # then you must also specify the character set. Examples include + # UTF-8, ISO-8859-1, and Shift_JIS. Defaults to 7-bit ASCII. + # @option options [String] :body_html_charset The character set of the + # +:body_html+ string. If the text must contain any other characters, + # then you must also specify the character set. Examples include + # UTF-8, ISO-8859-1, and Shift_JIS. Defaults to 7-bit ASCII. + # @option options [String] :body_html + # @return [nil] + def send_email options = {} + + require_each(options, :subject, :from) + require_one_of(options, :to, :cc, :bcc) + require_one_of(options, :body_text, :body_html) + + # these three options can be passed strings or arrays of strings, + # but the service requires them in a list (array) + [:to, :cc, :bcc, :reply_to].each do |key| + if options[key] + options[key] = [options[key]].flatten + end + end + + accepted_options = { + :subject => %w(message subject data), + :subject_charset => %w(message subject charset), + :to => %w(destination to_addresses), + :cc => %w(destination cc_addresses), + :bcc => %w(destination bcc_addresses), + :from => %w(source), + :reply_to => %w(reply_to_addresses), + :return_path => %w(return_path), + :body_text => %w(message body text data), + :body_text_charset => %w(message body text charset), + :body_html => %w(message body html data), + :body_html_charset => %w(message body html charset), + } + + client.send_email(nest_options(options, accepted_options)) + nil + + end + + # Sends a raw email (email message, with header and content specified). + # Useful for sending multipart MIME emails. The raw text of the message + # must comply with Internet email standards; otherwise, the message + # cannot be sent. + # + # raw = <<-EMAIL + # Date: Wed, 1 Jun 2011 09:13:07 -0700 + # Subject: A Sample Email + # From: "John Doe" + # To: "Jane Doe" + # Accept-Language: en-US + # Content-Language: en-US + # Content-Type: text/plain; charset="utf-8" + # Content-Transfer-Encoding: base64 + # MIME-Version: 1.0 + # + # c2FtcGxlIHRleHQNCg== + # EMAIL + # + # ses.send_raw_email(raw) + # + # Amazon SES has a limit on the total number of recipients per + # message: The combined number of To:, CC: and BCC: email addresses + # cannot exceed 50. If you need to send an email message to a larger + # audience, you can divide your recipient list into groups of 50 or + # fewer, and then call Amazon SES repeatedly to send the message to + # each group. + # + # @param [required, String] raw_message The raw text of the message. + # You can pass in any object whos #to_s returns a valid formatted + # email (e.g. ruby Mail gem). The raw message should: + # * Contain a header and a body, separated by a blank line + # * Contain all required internet email headers + # * Each part of a multipart MIME message must be formatted properly + # * MIME content types must be among those supported by Amazon SES. + # Refer to the Amazon SES Developer Guide for more details. + # * Use content that is base64-encoded, if MIME requires it + # @option options [String,Array] :to One or more email addresses to + # send the email to. + # @option options [String] :from The sender's email address. + # If you specify the :from option, then bounce notifications and + # complaints will be sent to this email address. This takes + # precedence over any Return-Path header that you might include in + # the +raw_message+. + # @return [nil] + def send_raw_email raw_message, options = {} + + send_opts = {} + send_opts[:raw_message] = {} + send_opts[:raw_message][:data] = raw_message.to_s + send_opts[:source] = options[:from] if options[:from] + send_opts[:destinations] = [options[:to]].flatten if options[:to] + + client.send_raw_email(send_opts) + nil + + end + + # for compatability with ActionMailer + alias_method :deliver, :send_raw_email + alias_method :deliver!, :send_raw_email + + # @example + # + # ses.quotas + # # {:max_24_hour_send=>200, :max_send_rate=>1.0, :sent_last_24_hours=>22} + # + # @return [Hash] Returns a hash of SES quotas and limits. + def quotas + Quotas.new(:config => config).to_h + end + + # Returns an array of email statistics. Each object in this array is a + # hash with the following keys: + # + # * +:delivery_attempts+ + # * +:rejects+ + # * +:bounces+ + # * +:complaints+ + # * +:timestamp+ + # + # @return [Array of Hashes] An array of email statistic hashes. + def statistics + client.get_send_statistics.send_data_points.inject([]) do |stats, data| + stats << { + :sent => data.timestamp, + :delivery_attempts => data.delivery_attempts, + :rejects => data.rejects, + :bounces => data.bounces, + :complaints => data.complaints, + } + end + end + + # @private + protected + def require_one_of options, *keys + unless keys.any?{|key| options[key] } + parts = keys.collect{|key| ":#{key}" }.join(', ') + raise ArgumentError, "you must provide at least one of #{parts}" + end + end + + # @private + protected + def require_each options, *keys + keys.each do |key| + unless options[key] + raise ArgumentError, "missing required option :#{key}" + end + end + end + + # @private + protected + def nest_options options, accepted_options + + send_opts = {} + accepted_options.each_pair do |option, keys| + next unless options[option] + hash = send_opts + keys.collect{|k| k.to_sym }.each do |key| + hash[key] = {} unless hash[key] + if keys.last == key.to_s + hash[key] = options[option] + else + hash = hash[key] + end + end + end + + send_opts + end + + end +end diff --git a/lib/aws/simple_email_service/client.rb b/lib/aws/simple_email_service/client.rb new file mode 100644 index 00000000000..1b10af1b235 --- /dev/null +++ b/lib/aws/simple_email_service/client.rb @@ -0,0 +1,39 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/base_client' +require 'aws/configured_client_methods' +require 'aws/simple_email_service/request' +require 'aws/simple_email_service/client/options' +require 'aws/simple_email_service/client/xml' +require 'aws/simple_email_service/errors' + +module AWS + class SimpleEmailService + + # @private + class Client < BaseClient + + include ConfiguredClientMethods + + API_VERSION = '2010-12-01' + + REGION_US_E1 = 'email.us-east-1.amazonaws.com' + + REQUEST_CLASS = SimpleEmailService::Request + + configure_client + + end + end +end diff --git a/lib/aws/simple_email_service/client/options.rb b/lib/aws/simple_email_service/client/options.rb new file mode 100644 index 00000000000..c3b39ca5e89 --- /dev/null +++ b/lib/aws/simple_email_service/client/options.rb @@ -0,0 +1,24 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +require 'aws/configured_option_grammars' +require 'aws/api_config_transform' + +module AWS + class SimpleEmailService + class Client < BaseClient + + # @private + module Options + + include ConfiguredOptionGrammars + + def self.api_config + ApiConfigTransform.rename_input_list_to_membered_list(super) + end + + define_configured_grammars + + end + end + end +end diff --git a/lib/aws/simple_email_service/client/xml.rb b/lib/aws/simple_email_service/client/xml.rb new file mode 100644 index 00000000000..66957d096e1 --- /dev/null +++ b/lib/aws/simple_email_service/client/xml.rb @@ -0,0 +1,38 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/configured_xml_grammars' +require 'aws/ignore_result_element' +require 'aws/xml_grammar' + +module AWS + class SimpleEmailService + class Client < BaseClient + + # @private + module XML + + include ConfiguredXmlGrammars + + extend IgnoreResultElement + + BaseError = XmlGrammar.customize do + element("Error") { ignore } + end + + define_configured_grammars + + end + end + end +end diff --git a/lib/aws/simple_email_service/email_address_collection.rb b/lib/aws/simple_email_service/email_address_collection.rb new file mode 100644 index 00000000000..5bb74b0b9a7 --- /dev/null +++ b/lib/aws/simple_email_service/email_address_collection.rb @@ -0,0 +1,66 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' + +module AWS + class SimpleEmailService + + # Helps you manage your verified SimpleEmailService email addresses. + class EmailAddressCollection + + include Model + include Enumerable + + # Requets for an email address to be verified. An email will be + # sent to the given +email_address+ with a link to click. Once + # the link has been followed the +email_address+ will be verified. + # + # @param [String] email_address The email address to verify. + # @return [nil] + def verify email_address + client.verify_email_address(:email_address => email_address) + nil + end + + alias_method :create, :verify + + # @param [String] email_address An email address to remove from the list + # of verified email addresses. Useful for cleanup as there is a 100 + # email address limit. + # @return [nil] + def delete email_address + client.delete_verified_email_address(:email_address => email_address) + nil + end + + def include? + # this is so jruby can detect that verified? is an alias + super + end + alias_method :verified?, :include? + + # Yields each verified email address as a string. + # @return [nil] + # yielded. + def each &block + response = client.list_verified_email_addresses({}) + response.verified_email_addresses.each do |email_address| + yield(email_address) + end + nil + end + + end + end +end diff --git a/lib/aws/simple_email_service/errors.rb b/lib/aws/simple_email_service/errors.rb new file mode 100644 index 00000000000..d59767fd43c --- /dev/null +++ b/lib/aws/simple_email_service/errors.rb @@ -0,0 +1,29 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/lazy_error_classes' +require 'aws/simple_email_service/client/xml' + +module AWS + class SimpleEmailService + + # @private + module Errors + + BASE_ERROR_GRAMMAR = Client::XML::BaseError + + include LazyErrorClasses + + end + end +end diff --git a/lib/aws/simple_email_service/quotas.rb b/lib/aws/simple_email_service/quotas.rb new file mode 100644 index 00000000000..a17b6f21089 --- /dev/null +++ b/lib/aws/simple_email_service/quotas.rb @@ -0,0 +1,64 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' + +module AWS + class SimpleEmailService + + # Returns information about your SimpleEmailService quotas. + class Quotas + + include Model + + # @return [Integer] The maximum number of emails the user is allowed + # to send in a 24-hour interval. + def max_24_hour_send + to_h[:max_24_hour_send] + end + + # @return [Float] The maximum number of emails the user is allowed + # to send per second. + def max_send_rate + to_h[:max_send_rate] + end + + # @return [Integer] Returns the number of emails sent during the + # previous 24 hours. + def sent_last_24_hours + to_h[:sent_last_24_hours] + end + + # @example + # + # @ses.quotas.to_h + # # {:max_24_hour_send=>200, :max_send_rate=>1.0, :sent_last_24_hours=>22} + # + # @return [Hash] Returns a hash of the SES quotas. + def to_h + response = client.get_send_quota({}) + { + :max_24_hour_send => response.max_24_hour_send.to_i, + :max_send_rate => response.max_send_rate.to_f, + :sent_last_24_hours => response.sent_last_24_hours.to_i, + } + end + + # @private + def inspect + "<#{self.class} #{to_h.inspect}>" + end + + end + end +end diff --git a/lib/aws/simple_email_service/request.rb b/lib/aws/simple_email_service/request.rb new file mode 100644 index 00000000000..8241d2a104e --- /dev/null +++ b/lib/aws/simple_email_service/request.rb @@ -0,0 +1,27 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/http/request' +require 'aws/authorize_v3' + +module AWS + class SimpleEmailService + + # @private + class Request < AWS::Http::Request + + include AuthorizeV3 + + end + end +end diff --git a/lib/aws/sns.rb b/lib/aws/sns.rb new file mode 100644 index 00000000000..2815d37aaac --- /dev/null +++ b/lib/aws/sns.rb @@ -0,0 +1,69 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/common' +require 'aws/service_interface' +require 'aws/sns/errors' +require 'aws/sns/client' +require 'aws/sns/topic_collection' +require 'aws/sns/subscription_collection' +require 'aws/sns/policy' + +module AWS + + # This class is the starting point for working with Amazon Simple + # Notification Service (SNS). + # + # To use Amazon SNS you must first {sign up here}[http://aws.amazon.com/sns/]. + # + # For more information about Amazon SNS: + # + # * {Amazon SNS}[http://aws.amazon.com/simpledb/] + # * {Amazon SNS Documentation}[http://aws.amazon.com/documentation/sns/] + # + # = Credentials + # + # You can setup default credentials for all AWS services via + # AWS.config: + # + # AWS.config( + # :access_key_id => 'YOUR_ACCESS_KEY_ID', + # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') + # + # Or you can set them directly on the SNS interface: + # + # sns = AWS::SNS.new( + # :access_key_id => 'YOUR_ACCESS_KEY_ID', + # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') + # + # = Understanding the SNS Interface + # + # ... + class SNS + + include ServiceInterface + + # @return [TopicCollection] Returns a topic collection for managing + # SNS topics. + def topics + TopicCollection.new(:config => config) + end + + # @return [SubscriptionCollection] Returns a subscription + # collection for managing SNS subscriptions. + def subscriptions + SubscriptionCollection.new(:config => config) + end + + end +end diff --git a/lib/aws/sns/client.rb b/lib/aws/sns/client.rb new file mode 100644 index 00000000000..e8010219501 --- /dev/null +++ b/lib/aws/sns/client.rb @@ -0,0 +1,37 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/base_client' +require 'aws/configured_client_methods' +require 'aws/sns/request' +require 'aws/sns/client/xml' +require 'aws/sns/client/options' + +module AWS + class SNS + + # @private + class Client < BaseClient + + include ConfiguredClientMethods + + API_VERSION = '2010-03-31' + + # @private + REQUEST_CLASS = SNS::Request + + configure_client + + end + end +end diff --git a/lib/aws/sns/client/options.rb b/lib/aws/sns/client/options.rb new file mode 100644 index 00000000000..64305e762f3 --- /dev/null +++ b/lib/aws/sns/client/options.rb @@ -0,0 +1,24 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +require 'aws/configured_option_grammars' +require 'aws/api_config_transform' + +module AWS + class SNS + class Client < BaseClient + + # @private + module Options + + include ConfiguredOptionGrammars + + def self.api_config + ApiConfigTransform.rename_input_list_to_membered_list(super) + end + + define_configured_grammars + + end + end + end +end diff --git a/lib/aws/sns/client/xml.rb b/lib/aws/sns/client/xml.rb new file mode 100644 index 00000000000..88921386801 --- /dev/null +++ b/lib/aws/sns/client/xml.rb @@ -0,0 +1,38 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/configured_xml_grammars' +require 'aws/ignore_result_element' +require 'aws/xml_grammar' + +module AWS + class SNS + class Client < BaseClient + + # @private + module XML + + include ConfiguredXmlGrammars + + extend IgnoreResultElement + + BaseError = XmlGrammar.customize do + element("Error") { ignore } + end + + define_configured_grammars + + end + end + end +end diff --git a/lib/aws/sns/errors.rb b/lib/aws/sns/errors.rb new file mode 100644 index 00000000000..199d0c8459c --- /dev/null +++ b/lib/aws/sns/errors.rb @@ -0,0 +1,29 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/lazy_error_classes' +require 'aws/sns/client/xml' + +module AWS + class SNS + + # @private + module Errors + + BASE_ERROR_GRAMMAR = Client::XML::BaseError + + include LazyErrorClasses + + end + end +end diff --git a/lib/aws/sns/policy.rb b/lib/aws/sns/policy.rb new file mode 100644 index 00000000000..2aee92eb941 --- /dev/null +++ b/lib/aws/sns/policy.rb @@ -0,0 +1,49 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/policy' + +module AWS + class SNS + + # @private + class Policy < AWS::Policy + + class Statement < AWS::Policy::Statement + + ACTION_MAPPING = { + :add_permission => 'sns:AddPermission', + :delete_topic => 'sns:DeleteTopic', + :get_topic_attributes => 'sns:GetTopicAttributes', + :list_subscriptions_by_topic => 'sns:ListSubscriptionsByTopic', + :publish => 'sns:Publish', + :receive => 'sns:Receive', + :remove_permission => 'sns:RemovePermission', + :set_topic_attributes => 'sns:SetTopicAttributes', + :subscribe => 'sns:Subscribe', + } + + protected + def resource_arn resource + case resource + when Topic then resource.arn + #when Subscription then resource.arn + else super(resource) + end + end + + end + + end + end +end diff --git a/lib/aws/sns/request.rb b/lib/aws/sns/request.rb new file mode 100644 index 00000000000..bcb964689d8 --- /dev/null +++ b/lib/aws/sns/request.rb @@ -0,0 +1,27 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/http/request' +require 'aws/authorize_v2' + +module AWS + class SNS + + # @private + class Request < AWS::Http::Request + + include AuthorizeV2 + + end + end +end diff --git a/lib/aws/sns/subscription.rb b/lib/aws/sns/subscription.rb new file mode 100644 index 00000000000..14e1b45e70b --- /dev/null +++ b/lib/aws/sns/subscription.rb @@ -0,0 +1,100 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/sns/topic_subscription_collection' +require 'aws/sns/subscription_collection' + +module AWS + class SNS + + # Represents a subscription of a single endpoint to an SNS topic. + # To create a subscription, use the {Topic#subscribe} method. + # Depending on the endpoint type, you may also need to use + # {Topic#confirm_subscription}. + class Subscription + + include Model + + # @private + def initialize(arn, opts = {}) + @arn = arn + @topic = opts[:topic] + @endpoint = opts[:endpoint] + @protocol = opts[:protocol] + @owner_id = opts[:owner_id] + super + end + + # @return [String] The ARN of the subscription. + attr_reader :arn + + # @return [Topic] The topic to which the endpoint is subscribed. + attr_reader :topic + + # @return [String] The endpoint. This can be an HTTP or HTTPS + # URL, an e-mail address, or a queue ARN. + attr_reader :endpoint + + # @return [String] The protocol. Possible values: + # + # * +:http+ + # * +:https+ + # * +:email+ + # * +:email_json+ + # * +:sqs+ + attr_reader :protocol + + # @return [String] The AWS account ID of the subscription owner. + attr_reader :owner_id + + # Deletes this subscription. + # @return [nil] + def unsubscribe + client.unsubscribe(:subscription_arn => arn) + nil + end + + # @note This method requests the entire list of subscriptions + # for the topic (if known) or the account (if the topic is not + # known). It can be expensive if the number of subscriptions + # is high. + # + # @return [Boolean] Returns true if the subscription exists. + def exists? + collection = + if topic + TopicSubscriptionCollection.new(topic, + :config => config) + else + SubscriptionCollection.new(:config => config) + end + collection.include?(self) + end + + # @private + def inspect + "<#{self.class}:#{arn}>" + end + + # @return [Boolean] Returns true if the subscriptions have the same + # resource ARN. + def ==(other) + other.kind_of?(Subscription) and other.arn == arn + end + alias_method :eql?, :== + + end + + end +end diff --git a/lib/aws/sns/subscription_collection.rb b/lib/aws/sns/subscription_collection.rb new file mode 100644 index 00000000000..610b2cac45e --- /dev/null +++ b/lib/aws/sns/subscription_collection.rb @@ -0,0 +1,84 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/sns/subscription' +require 'aws/sns/topic' + +module AWS + class SNS + + # Represents the collection of all subscriptions for the AWS + # account. For example: + # + # # get the ARNs of all SQS queues with subscriptions to topics + # # owned by this account + # topic.subscriptions. + # select { |s| s.protocol == :sqs }. + # collect(&:endpoint) + # + class SubscriptionCollection + + include Model + include Enumerable + + # Yield each subscription belonging to this account. + # @yieldparam [Subscription] subscription Each of the + # subscriptions in the account. + # @return [nil] + def each + next_token = nil + begin + opts = request_opts + opts[:next_token] = next_token if next_token + resp = client.send(list_request, opts) + resp.subscriptions.each do |sub| + subscription = Subscription.new(sub.subscription_arn, + :endpoint => sub.endpoint, + :protocol => sub.protocol.tr('-','_').to_sym, + :owner_id => sub.owner, + :topic => Topic.new(sub.topic_arn, :config => config), + :config => config + ) + yield(subscription) + end + next_token = resp.next_token + end until resp && next_token.nil? + nil + end + + # Retrieves a subscription object by ARN. This method does not + # make any requests to the service. + # + # @param [String] arn The ARN of the subscription to retrieve. + # @return [Subscription] The subscription with the given ARN. + def [] arn + Subscription.new(arn, :config => config) + end + + # @private + protected + def list_request + :list_subscriptions + end + + # @private + protected + def request_opts + {} + end + + end + + end +end diff --git a/lib/aws/sns/topic.rb b/lib/aws/sns/topic.rb new file mode 100644 index 00000000000..4cafb66b4ca --- /dev/null +++ b/lib/aws/sns/topic.rb @@ -0,0 +1,384 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'json' +require 'aws/model' +require 'aws/sns/policy' +require 'aws/sns/subscription' +require 'aws/sns/topic_subscription_collection' +require 'aws/sqs' + +module AWS + class SNS + + class Topic + + include Model + + # @param [String] arn The topic ARN. + def initialize arn, options = {} + @arn = arn + super + end + + # @return [String] The topic ARN. + attr_reader :arn + + # The topic name. + # + # If you have not set a display name (see {#display_name=}) then + # this is used as the "From" field for notifications to email and + # email-json endpoints. + # @return [String] Returns the toipc name. + def name + arn.split(/:/)[-1] + end + + # Causes the given +endpoint+ to receive messages published to this + # topic. + # + # == Subscribing to SQS Queues + # + # If you subscribe to an SQS queue (with a {SQS::Queue} object} + # then a policy will be added/updated to the queue that will + # permit this topic to send it messages. Some important notes: + # + # * If you subscribe with a queue by ARN then you must change the + # policy yourself. + # + # * If you do not want the policy modified then pass +:update_policy+ + # as false or just pass the queue's arn + # + # topic.subscribe(queue.arn) + # topic.subscribe(queue, :update_policy => false) + # + # @example Using a url string to set the endpoint (http and https) + # + # topic.subscribe('http://example.com/messages') + # topic.subscribe('https://example.com/messages') + # + # @example Using a uri object to set the endpoint (http and https) + # + # topic.subscribe(URI.parse('http://example.com/messages')) + # topic.subscribe(URI.parse('https://example.com/messages')) + # + # @example Email address as endpoint + # + # topic.subscribe('nobody@example.com') + # + # @example Email address as a JSON endpoint + # + # # send messages encoded as json object to the given email address + # topic.subscribe('nobody@example.com', :json => true) + # + # @example SQS Queue (by arn) + # + # # you must manage the queue policy yourself to allow the + # # the topic to send messages (policy action 'sqs:SendMessage') + # topic.subscribe('arn:aws:sqs:us-east-1:123456789123:AQueue') + # + # @example SQS Queue (by Queue object) + # + # # the queue policy will be added/updated to allow the topic + # # to send it messages + # topic.subscribe(AWS::SQS.new.queues.first) + # + # @param [mixed] endpoint The endpoint that should receive + # messages that are published to this topic. Valid values + # for +endpoint+ include: + # * URI object + # * http and https URI strings + # * email addresse + # * {SQS::Queue} + # * SQS queue ARN + # @param [Hash] options + # @option options [Boolean] :json (false) + # @return [Subscription,nil] Returns a subscription when possible. + # If the subscription requires confirmation first, then +nil+ is + # returned instead. + def subscribe(endpoint, opts = {}) + subscribe_opts = endpoint_opts(endpoint, opts).merge(:topic_arn => arn) + resp = client.subscribe(subscribe_opts) + if arn = resp.subscription_arn and arn =~ /^arn:/ + Subscription.new(arn, :config => config) + else + nil + end + end + + # Verifies an endpoint owner's intent to receive messages by + # validating the token sent to the endpoint by an earlier + # Subscribe action. If the token is valid, the action creates a + # new subscription. + # + # @param [String] token Short-lived token sent to an endpoint + # during the {#subscribe} action. + # + # @param [Hash] options Additional options for confirming the + # subscription. + # + # @option :options [Boolean] :authenticate_on_unsubscribe + # Indicates that you want to disable unauthenticated + # unsubsciption of the subscription. If parameter is present + # in the request, the request has an AWS signature, and the + # value of this parameter is true, only the topic owner and + # the subscription owner will be permitted to unsubscribe the + # endpoint, and the Unsubscribe action will require AWS + # authentication. + # + # @return [Subscription] The newly created subscription. + def confirm_subscription(token, opts = {}) + confirm_opts = opts.merge(:token => token, :topic_arn => arn) + resp = client.confirm_subscription(confirm_opts) + Subscription.new( + resp.subscription_arn, + :topic => self, + :config => config) + end + + # @return [TopicSubscriptionCollection] Returns a collection that + # represents all of the subscriptions for this topic. + def subscriptions + TopicSubscriptionCollection.new(self) + end + + # @return [String] Returns the human-readable name used in + # the "From" field for notifications to email and email-json + # endpoints. If you have not set the display name the topic + # {#name} will be used/returned instead. + def display_name + to_h[:display_name] + end + + # @param [String] display_name Sets the human-readable name used in + # the "From" field for notifications to email and email-json + # endpoints. + # @return [String] Returns the display_name as passed. + def display_name= display_name + set_attribute('DisplayName', display_name) + display_name + end + + # @return [String] The topic owner's ID. + def owner + to_h[:owner] + end + + # @return [Integer] Returns number of confirmed topic subscriptions. + def num_subscriptions_confirmed + to_h[:num_subscriptions_confirmed] + end + + # @return [Integer] Returns number of pending topic subscriptions. + def num_subscriptions_pending + to_h[:num_subscriptions_pending] + end + + # @return [Integer] Returns number of deleted topic subscriptions. + def num_subscriptions_deleted + to_h[:num_subscriptions_deleted] + end + + # @return [Policy] The topic's {Policy}. + def policy + to_h[:policy] + end + + # Sets the topic's policy. + # @param [String,Policy] policy A JSON policy string, a {Policy} object + # or any other object that responds to #to_json with a valid + # policy. + # @return [nil] + def policy= policy + policy_json = policy.is_a?(String) ? policy : policy.to_json + set_attribute('Policy', policy_json) + nil + end + + # Publishes a message to this SNS topic. + # + # topic.publish('a short message') + # + # You can pass a subject that is used when sending the message to + # email endpoints: + # + # topic.publish('message', :subject => 'SNS message subject') + # + # If you would like to pass a different message to various protocols + # (endpoint types) you can pass those as options: + # + # topic.publish('default message', + # :http => "message sent to http endpoints", + # :https => "message sent to https endpoints", + # :email => "message sent to email endpoints") + # + # The full list of acceptable protocols are listed below. The default + # message is sent to endpoints who's protocol was not listed. + # + # @param [String] default_message The message you want to send to the + # topic. Messages must be UTF-8 encoded strings at most 8 KB in size + # (8192 bytes, not 8192 characters). + # @param [Hash] Options + # @option options [String] :subject Used as the "Subject" line when + # the message is delivered to email endpoints. Will also be + # included in the standard JSON messages delivered to other endpoints. + # * must be ASCII text that begins with a letter, number or + # punctuation mark + # * must not include line breaks or control characters + # * and must be less than 100 characters long + # @option options [String] :http - Message to use when sending to an + # HTTP endpoint. + # @option options [String] :https - Message to use when sending to an + # HTTPS endpoint. + # @option options [String] :email - Message to use when sending to an + # email endpoint. + # @option options [String] :email_json - Message to use when sending + # to an email json endpoint. + # @option options [String] :sqs - Message to use when sending to an + # SQS endpoint. + # @return [String] Returns the ID of the message that was sent. + def publish default_message, options = {} + + message = { :default => default_message } + + [:http, :https, :email, :email_json, :sqs].each do |protocol| + if options[protocol] + message[protocol.to_s.gsub(/_/, '-')] = options[protocol] + end + end + + publish_opts = {} + publish_opts[:message] = message.to_json + publish_opts[:message_structure] = 'json' + publish_opts[:subject] = options[:subject] if options[:subject] + publish_opts[:topic_arn] = arn + + response = client.publish(publish_opts) + + response.message_id + + end + + # Deletes the topic. + # @return [nil] + def delete + client.delete_topic(:topic_arn => arn) + nil + end + + # @return [Hash] Returns a hash of attributes about this topic, + # including: + # + # * +:arn+ + # * +:name+ + # * +:owner+ + # * +:display_name+ + # * +:policy+ + # * +:num_subscriptions_confirmed+ + # * +:num_subscriptions_pending+ + # * +:num_subscriptions_deleted+ + # + def to_h + attributes = client.get_topic_attributes(:topic_arn => arn).attributes + { + :arn => arn, + :name => name, + :owner => attributes['Owner'], + :display_name => attributes['DisplayName'] || name, + :policy => parse_policy(attributes['Policy']), + :num_subscriptions_confirmed => attributes['SubscriptionsConfirmed'].to_i, + :num_subscriptions_pending => attributes['SubscriptionsPending'].to_i, + :num_subscriptions_deleted => attributes['SubscriptionsDeleted'].to_i, + } + end + + # @return [Boolean] Returns true if compared to another {Topic} + # with the same ARN. + def ==(other) + other.kind_of?(Topic) and other.arn == arn + end + alias_method :eql?, :== + + # @private + protected + def parse_policy policy_json + if policy_json + policy = SNS::Policy.from_json(policy_json) + policy.extend(PolicyProxy) + policy.topic = self + policy + else + nil + end + end + + # @private + protected + def set_attribute name, value + client.send(:set_topic_attributes, { + :topic_arn => arn, + :attribute_name => name, + :attribute_value => value, + }) + end + + # @private + module PolicyProxy + attr_accessor :topic + def change + yield(self) + topic.policy = self + end + end + + # @private + protected + def endpoint_opts(endpoint, opts = {}) + + case + when endpoint.is_a?(SQS::Queue) + + # auto add a policy to the queue to allow the topic + # to send the queue messages + unless opts[:update_policy] == false + policy = endpoint.policy || SQS::Policy.new + policy.allow( + :principal => :any, + :actions => [:send_message], + :resources => [endpoint] + ).where(:source_arn).is(arn) + endpoint.policy = policy + end + + { :protocol => 'sqs', :endpoint => endpoint.arn } + + when endpoint =~ /^arn:/ + raise ArgumentError, "expected a queue ARN" unless + endpoint =~ /^arn:aws:sqs:/ + { :protocol => "sqs", :endpoint => endpoint } + when endpoint.kind_of?(URI) + { :protocol => endpoint.scheme, + :endpoint => endpoint.to_s } + when endpoint =~ /^(https?):/ + { :protocol => $1, :endpoint => endpoint } + when endpoint.include?("@") + { :protocol => opts[:json] ? "email-json" : "email", + :endpoint => endpoint } + else + raise ArgumentError, "could not determine protocol for '#{endpoint}'" + end + end + + end + end +end diff --git a/lib/aws/sns/topic_collection.rb b/lib/aws/sns/topic_collection.rb new file mode 100644 index 00000000000..66a27ef2d4f --- /dev/null +++ b/lib/aws/sns/topic_collection.rb @@ -0,0 +1,70 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/sns/topic' + +module AWS + class SNS + + class TopicCollection + + include Model + include Enumerable + + + # Creates and returns a new SNS Topic. + # @return [Topic] Returns a new topic with the given name. + def create name + response = client.create_topic(:name => name) + Topic.new(response.topic_arn, :config => config) + end + + # @param [String] topic_arn An AWS SNS Topic ARN. It should be + # formatted something like: + # + # arn:aws:sns:us-east-1:123456789012:TopicName + # + # @return [Topic] Returns a topic with the given Topic ARN. + def [] topic_arn + unless topic_arn =~ /^arn:aws:sns:/ + raise ArgumentError, "invalid topic arn `#{topic_arn}`" + end + Topic.new(topic_arn, :config => config) + end + + # Yields once for each topic. + # @yieldparam [Topic] topic + # @return [nil] + def each &block + + next_token = nil + + begin + + list_options = next_token ? { :next_token => next_token } : {} + response = client.list_topics(list_options) + + response.topics.each do |t| + topic = Topic.new(t.topic_arn, :config => config) + yield(topic) + end + + end while(next_token = response.next_token) + nil + + end + + end + end +end diff --git a/lib/aws/sns/topic_subscription_collection.rb b/lib/aws/sns/topic_subscription_collection.rb new file mode 100644 index 00000000000..e4d83458081 --- /dev/null +++ b/lib/aws/sns/topic_subscription_collection.rb @@ -0,0 +1,58 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/sns/subscription_collection' + +module AWS + class SNS + + # Represents the collection of all subscriptions for a particular + # topic. For example: + # + # # get the e-mail addressess that receive plain-text + # # messages sent to the topic + # topic.subscriptions. + # select { |s| s.protocol == :email }. + # map(&:endpoint) + class TopicSubscriptionCollection < SubscriptionCollection + + include Model + include Enumerable + + # @return [Topic] The topic to which all the subscriptions + # belong. + attr_reader :topic + + # @private + def initialize(topic, opts = {}) + @topic = topic + super + end + + # @private + protected + def list_request + :list_subscriptions_by_topic + end + + # @private + protected + def request_opts + { :topic_arn => topic.arn } + end + + end + + end +end diff --git a/lib/aws/sqs.rb b/lib/aws/sqs.rb new file mode 100644 index 00000000000..b0be59e1eb6 --- /dev/null +++ b/lib/aws/sqs.rb @@ -0,0 +1,70 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/common' +require 'aws/service_interface' +require 'aws/sqs/client' +require 'aws/sqs/queue_collection' + +module AWS + + # Provides an expressive, object-oriented interface to Amazon SQS. + # + # == Credentials + # + # You can setup default credentials for all AWS services via + # AWS.config: + # + # AWS.config( + # :access_key_id => 'YOUR_ACCESS_KEY_ID', + # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') + # + # Or you can set them directly on the SQS interface: + # + # sqs = AWS::SQS.new( + # :access_key_id => 'YOUR_ACCESS_KEY_ID', + # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') + # + # == Queues and Messages + # + # Amazon SQS is a distributed queue system that enables web + # service applications to quickly and reliably queue messages that + # one component in the application generates to be consumed by + # another component. A queue is a temporary repository for + # messages that are awaiting processing. + # + # You can access your queues using the {#queues} collection. For + # example, to create a queue, use {QueueCollection#create}: + # + # queue = sqs.queues.create("myqueue") + # + # Or to find out what queues you have in your account: + # + # pp sqs.queues.collect(&:url) + # + # See the {Queue} class for more information on how to send and + # receive messages. + # + class SQS + + include ServiceInterface + + # @return [QueueCollection] The collection of all {Queue} + # objects in your account. + def queues + QueueCollection.new(:config => config) + end + + end + +end diff --git a/lib/aws/sqs/client.rb b/lib/aws/sqs/client.rb new file mode 100644 index 00000000000..58c4d45ed45 --- /dev/null +++ b/lib/aws/sqs/client.rb @@ -0,0 +1,38 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/base_client' +require 'aws/configured_client_methods' +require 'aws/sqs/request' +require 'aws/sqs/client/xml' +require 'aws/sqs/errors' +require 'aws/sqs/policy' + +module AWS + class SQS + + # @private + class Client < BaseClient + + include ConfiguredClientMethods + + API_VERSION = '2009-02-01' + + # @private + REQUEST_CLASS = SQS::Request + + configure_client + + end + end +end diff --git a/lib/aws/sqs/client/xml.rb b/lib/aws/sqs/client/xml.rb new file mode 100644 index 00000000000..cea97ad32b2 --- /dev/null +++ b/lib/aws/sqs/client/xml.rb @@ -0,0 +1,36 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/configured_xml_grammars' +require 'aws/ignore_result_element' + +module AWS + class SQS + class Client < BaseClient + + # @private + module XML + + include ConfiguredXmlGrammars + extend IgnoreResultElement + + BaseError = XmlGrammar.customize do + element("Error") { ignore } + end + + define_configured_grammars + + end + end + end +end diff --git a/lib/aws/sqs/errors.rb b/lib/aws/sqs/errors.rb new file mode 100644 index 00000000000..dbb323d8ce4 --- /dev/null +++ b/lib/aws/sqs/errors.rb @@ -0,0 +1,33 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/lazy_error_classes' +require 'aws/sqs/client/xml' + +module AWS + class SQS + + # @private + module Errors + + BASE_ERROR_GRAMMAR = Client::XML::BaseError + + include LazyErrorClasses + + def self.error_class(code) + super(code.sub(/^AWS\.SimpleQueueService\./, '')) + end + + end + end +end diff --git a/lib/aws/sqs/policy.rb b/lib/aws/sqs/policy.rb new file mode 100644 index 00000000000..356ec005e89 --- /dev/null +++ b/lib/aws/sqs/policy.rb @@ -0,0 +1,50 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/policy' + +module AWS + class SQS + + # @private + class Policy < AWS::Policy + + class Statement < AWS::Policy::Statement + + ACTION_MAPPING = { + :add_permission => 'sqs:AddPermission', + :change_message_visibility => 'sqs:ChangeMessageVisibility', + :create_queue => 'sqs:CreateQueue', + :delete_message => 'sqs:DeleteMessage', + :delete_queue => 'sqs:DeleteQueue', + :get_queue_attributes => 'sqs:GetQueueAttributes', + :list_queues => 'sqs:ListQueues', + :receive_message => 'sqs:ReceiveMessage', + :remove_permission => 'sqs:RemovePermission', + :send_message => 'sqs:SendMessage', + :set_queue_attributes => 'sqs:SetQueueAttributes', + } + + protected + def resource_arn resource + case resource + when Queue then URI.parse(resource.url).path + else resource.to_s + end + end + + end + + end + end +end diff --git a/lib/aws/sqs/queue.rb b/lib/aws/sqs/queue.rb new file mode 100644 index 00000000000..f6202c8af09 --- /dev/null +++ b/lib/aws/sqs/queue.rb @@ -0,0 +1,507 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/inflection' +require 'aws/model' +require 'aws/sqs/received_message' +require 'aws/sqs/received_sns_message' + +module AWS + class SQS + + # Represents an Amazon SQS Queue. + # + # @example Sending a message + # msg = queue.send_message("HELLO") + # puts "Sent message: #{msg.id}" + # + # @example Polling for messages indefinitely + # queue.poll do |msg| + # puts "Got message: #{msg.body}" + # end + # + class Queue + + # The default number of seconds to wait between polling requests for + # new messages. + DEFAULT_POLL_INTERVAL = 1 + + include Model + + # @return [String] The queue URL. + attr_reader :url + + # @private + def initialize(url, opts = {}) + @url = url + super + end + + # Deletes the queue, regardless of whether it is empty. + # + # When you delete a queue, the deletion process takes up to 60 + # seconds. Requests you send involving that queue during the + # 60 seconds might succeed. For example, calling + # {#send_message} might succeed, but after the 60 seconds, the + # queue and that message you sent no longer exist. + # + # Also, when you delete a queue, you must wait at least 60 seconds + # before creating a queue with the same name. + # @return [nil] + def delete + client.delete_queue(:queue_url => url) + nil + end + + # Represents a message sent using {Queue#send_message}. + class SentMessage + + # @return [String] Returns the message ID. + attr_accessor :message_id + + alias_method :id, :message_id + + # @return [String] Returns an MD5 digest of the message body + # string. You can use this to verify that SQS received your + # message correctly. + attr_accessor :md5 + + end + + # Delivers a message to this queue. + # + # @param [String] body The message to send. The maximum + # allowed message size is 64 KB. The message may only + # contain Unicode characters from the following list, + # according to the W3C XML specification (for more + # information, go to + # http://www.w3.org/TR/REC-xml/#charsets). If you send any + # characters not included in the list, your request will be + # rejected. + # + # * #x9 + # * #xA + # * #xD + # * #x20 to #xD7FF + # * #xE000 to #xFFFD + # * #x10000 to #x10FFFF + # + # @return [SentMessage] An object containing information about + # the message that was sent. + def send_message(body) + resp = client.send_message(:queue_url => url, + :message_body => body) + msg = SentMessage.new + msg.message_id = resp.message_id + msg.md5 = resp.md5_of_message_body + msg + end + + # Retrieves one or more messages. When a block is given, each + # message is yielded to the block and then deleted as long as + # the block exits normally. When no block is given, you must + # delete the message yourself using {ReceivedMessage#delete}. + # + # @note Due to the distributed nature of the queue, a weighted + # random set of machines is sampled on a ReceiveMessage + # call. That means only the messages on the sampled machines + # are returned. If the number of messages in the queue is + # small (less than 1000), it is likely you will get fewer + # messages than you requested per call to + # {#receive_message}. If the number of messages in the queue + # is extremely small, you might not receive any messages. + # To poll continually for messages, use the {#poll} method, + # which automatically retries the request after a + # configurable delay. + # + # @param [Hash] opts Options for receiving messages. + # + # @option opts [Integer] :limit The maximum number of messages + # to receive. By default this is 1, and the return value is + # a single message object. If this options is specified and + # is not 1, the return value is an array of message objects; + # however, the array may contain fewer objects than you + # requested. Valid values: integers from 1 to 10. + # + # Not necessarily all the messages in the queue are returned + # (for more information, see the preceding note about + # machine sampling). + # + # @option opts [Integer] :visibilitiy_timeout The duration (in + # seconds) that the received messages are hidden from + # subsequent retrieve requests. Valid values: integer from + # 0 to 43200 (maximum 12 hours) + # + # @option opts [Array] :attributes The + # attributes to populate in each received message. Valid values: + # + # * +:all+ (to populate all attributes) + # * +:sender_id+ + # * +:sent_at+ + # * +:receive_count+ + # * +:first_received_at+ + # + # See {ReceivedMessage} for documentation on each + # attribute's meaning. + # + # @yieldparam [ReceivedMessage] message Each message that was received. + # + # @return [ReceivedMessage] Returns the received message (or messages) + # only if a block is not given to this method. + # + def receive_message(opts = {}, &block) + resp = client.receive_message(receive_opts(opts)) + + messages = resp.messages.map do |m| + ReceivedMessage.new(self, m.message_id, m.receipt_handle, + :body => m.body, + :md5 => m.md5_of_body, + :attributes => m.attributes) + end + + if block + call_message_block(messages, block) + elsif opts[:limit] && opts[:limit] != 1 + messages + else + messages.first + end + end + alias_method :receive_messages, :receive_message + + # Polls continually for messages. For example, you can use + # this to poll indefinitely: + # + # queue.poll { |msg| puts msg.body } + # + # Or, to poll indefinitely for the first message and then + # continue polling until no message is received for a period + # of at least ten seconds: + # + # queue.poll(:initial_timeout => false, + # :idle_timeout => 10) { |msg| puts msg.body } + # + # As with the block form of {#receive_message}, this method + # automatically deletes the message then the block exits + # normally. + # + # @yieldparam [ReceivedMessage] message Each message that was received. + # + # @param [Hash] opts Options for polling. + # + # @option opts [Float, Integer] :poll_interval The number of + # seconds to wait before retrying when no messages are + # received. The default is 1 second. + # + # @option opts [Integer] :idle_timeout The maximum number of + # seconds to spend polling while no messages are being + # returned. By default this method polls indefinitely + # whether messages are received or not. + # + # @option opts [Integer] :initial_timeout The maximum number + # of seconds to spend polling before the first message is + # received. This option defaults to the value of + # +:idle_timeout+. You can specify +false+ to poll + # indefinitely for the first message when +:idle_timeout+ is + # set. + # + # @option opts [Integer] :batch_size The maximum number of + # messages to retrieve in a single request. By default + # messages are received one at a time. Valid values: + # integers from 1 to 10. + # + # @option opts [Integer] :visibilitiy_timeout The duration (in + # seconds) that the received messages are hidden from + # subsequent retrieve requests. Valid values: integer from + # 0 to 43200 (maximum 12 hours) + # + # @option opts [Array] :attributes The + # attributes to populate in each received message. Valid values: + # + # * +:all+ (to populate all attributes) + # * +:sender_id+ + # * +:sent_at+ + # * +:receive_count+ + # * +:first_received_at+ + # + # See {ReceivedMessage} for documentation on each + # attribute's meaning. + # + # @return [nil] + def poll(opts = {}, &block) + poll_interval = opts[:poll_interval] || DEFAULT_POLL_INTERVAL + opts[:limit] = opts.delete(:batch_size) if + opts.key?(:batch_size) + + last_message_at = Time.now + got_first = false + loop do + got_msg = false + receive_messages(opts) do |message| + got_msg = got_first = true + last_message_at = Time.now + yield(message) + end + unless got_msg + Kernel.sleep(poll_interval) unless poll_interval == 0 + return if hit_timeout?(got_first, last_message_at, opts) + end + end + nil + end + + # @return [Integer] The approximate number of visible messages + # in a queue. For more information, see {Resources Required + # to Process + # Messages}[http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/IntroductionArticle.html#ApproximateNumber] + # in the Amazon SQS Developer Guide. + def approximate_number_of_messages + get_attribute("ApproximateNumberOfMessages").to_i + end + alias_method :visible_messages, :approximate_number_of_messages + + # @return [Integer] The approximate number of messages that + # are not timed-out and not deleted. For more information, + # see {Resources Required to Process + # Messages}[http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/IntroductionArticle.html#ApproximateNumber] + # in the Amazon SQS Developer Guide. + def approximate_number_of_messages_not_visible + get_attribute("ApproximateNumberOfMessagesNotVisible").to_i + end + alias_method :invisible_messages, :approximate_number_of_messages_not_visible + + # @return [Integer] Returns the visibility timeout for the + # queue. For more information about visibility timeout, see + # {Visibility + # Timeout}[http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/IntroductionArticle.html#AboutVT] + # in the Amazon SQS Developer Guide. + def visibility_timeout + get_attribute("VisibilityTimeout").to_i + end + + # Sets the visibility timeout for the queue. + # + # @param [Integer] timeout The length of time (in seconds) + # that a message received from a queue will be invisible to + # other receiving components when they ask to receive + # messages. Valid values: integers from 0 to 43200 (12 + # hours). + # + # @return Returns the value passed as a timeout. + def visibility_timeout=(timeout) + set_attribute("VisibilityTimeout", timeout.to_s) + timeout + end + + # @return [Time] The time when the queue was created. + def created_timestamp + Time.at(get_attribute("CreatedTimestamp").to_i) + end + + # @return [Time] The time when the queue was last changed. + def last_modified_timestamp + Time.at(get_attribute("LastModifiedTimestamp").to_i) + end + + # @return [Integer] The limit of how many bytes a message can + # contain before Amazon SQS rejects it. + def maximum_message_size + get_attribute("MaximumMessageSize").to_i + end + + # Sets the maximum message size for the queue. + # + # @param [Integer] size The limit of how many bytes a message + # can contain before Amazon SQS rejects it. This must be an + # integer from 1024 bytes (1KB) up to 65536 bytes + # (64KB). The default for this attribute is 8192 (8KB). + # @return Retuns the passed size argument. + def maximum_message_size=(size) + set_attribute("MaximumMessageSize", size.to_s) + end + + # @return [Integer] The number of seconds Amazon SQS retains a + # message. + def message_retention_period + get_attribute("MessageRetentionPeriod").to_i + end + + # Sets the message retention period for the queue + # + # @param [Integer] period The number of seconds Amazon SQS + # retains a message. Must be an integer from 3600 (1 hour) + # to 1209600 (14 days). The default for this attribute is + # 345600 (4 days). + # @return Returns the passed period argument. + def message_retention_period=(period) + set_attribute("MessageRetentionPeriod", period.to_s) + period + end + + # @return [String] The queue's Amazon resource name (ARN). + def arn + @arn ||= get_attribute("QueueArn") + end + + # @return [Boolean] True if the queue exists. + # + # @note This may raise an exception if you don't have + # permission to access the queue attributes. Also, it may + # return true for up to 60 seconds after a queue has been + # deleted. + def exists? + client.get_queue_attributes(:queue_url => url, + :attribute_names => ["QueueArn"]) + rescue Errors::NonExistentQueue, Errors::InvalidAddress + false + else + true + end + + # @private + module PolicyProxy + + attr_accessor :queue + + def change + yield(self) + queue.policy = self + end + + def delete + queue.client.send(:set_attribute, 'Policy', '') + end + + end + + # @return [Policy] Returns the current queue policy if there is one. + # Returns +nil+ otherwise. + def policy + if policy_json = get_attribute('Policy') + policy = SQS::Policy.from_json(policy_json) + policy.extend(PolicyProxy) + policy.queue = self + policy + else + nil + end + end + + # Set the policy on this queue. + # + # If you pass nil or an empty string then it will have the same + # effect as deleting the policy. + # + # @param policy The policy to set. This policy can be a {Policy} object, + # a json policy string, or any other object that responds with a policy + # string when it received #to_json. + # + # @return [nil] + def policy= policy + policy_string = case policy + when nil, '' then '' + when String then policy + else policy.to_json + end + set_attribute('Policy', policy_string) + nil + end + + # @return [Boolean] Returns true if the other queue has the same + # url. + def ==(other) + other.kind_of?(Queue) and other.url == url + end + alias_method :eql?, :== + + # @private + def inspect + "<#{self.class}:#{url}>" + end + + # @private + protected + def hit_timeout?(got_first, last_message_at, opts) + initial_timeout = opts[:initial_timeout] + idle_timeout = opts[:idle_timeout] + + timeout = (got_first || + # if initial_timeout is false (as opposed + # to nil) then we skip the branch and poll + # indefinitely until the first message + # comes + (!initial_timeout && initial_timeout != false) ? + idle_timeout : + initial_timeout) and + Time.now - last_message_at > timeout + end + + # @private + protected + def receive_opts(opts) + receive_opts = { :queue_url => url } + receive_opts[:visibility_timeout] = opts[:visibility_timeout] if + opts[:visibility_timeout] + receive_opts[:max_number_of_messages] = opts[:limit] if + opts[:limit] + if names = opts[:attributes] + receive_opts[:attribute_names] = names.map do |name| + name = ReceivedMessage::ATTRIBUTE_ALIASES[name.to_sym] if + ReceivedMessage::ATTRIBUTE_ALIASES.key?(name.to_sym) + name = Inflection.class_name(name.to_s) if name.kind_of?(Symbol) + name + end + end + receive_opts + end + + # @private + protected + def call_message_block(messages, block) + result = nil + messages.each do |message| + begin + result = block.call(message) + rescue Exception => e + raise + else + message.delete + end + end + result + end + + # @private + protected + def get_attribute(name) + resp = client.get_queue_attributes(:queue_url => url, + :attribute_names => + [name, "QueueArn"].uniq) + @arn ||= resp.attributes["QueueArn"] + resp.attributes[name] + end + + # @private + protected + def set_attribute(name, value) + client.set_queue_attributes(:queue_url => url, + :attribute => { + :name => name, + :value => value + }) + end + + end + + end +end diff --git a/lib/aws/sqs/queue_collection.rb b/lib/aws/sqs/queue_collection.rb new file mode 100644 index 00000000000..a0777a6ee76 --- /dev/null +++ b/lib/aws/sqs/queue_collection.rb @@ -0,0 +1,105 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'aws/sqs/queue' + +module AWS + class SQS + + # Represents all the {Queue} objects in your account. + # + # If you have permission to access a queue created by another + # account, you can also use this collection to access that queue + # by URL. + # + # @example Printing the URLs of all queues + # pp sqs.queues.map(&:url) + # + # @example Filtering queues by queue name prefix + # pp sqs.queues.with_prefix("production_").map(&:url) + # + # @example Accessing a queue by URL + # url = "http://sqs.us-east-1.amazonaws.com/123456789012/myqueue" + # sqs.queues[url].send_message("HELLO") + class QueueCollection + + include Model + include Enumerable + + # @private + def initialize(opts = {}) + @prefix = opts[:prefix] + super + end + + # @return [String] The queue name prefix by which this + # collection is filtered. + attr_reader :prefix + + # Creates a new queue. + # + # @note If you delete a queue, you must wait at least 60 + # seconds before creating a queue with the same name. + # + # @param [String] name The name to use for the queue created. + # Constraints: Maximum 80 characters; alphanumeric + # characters, hyphens (-), and underscores (_) are allowed. + # + # To successfully create a new queue, you must provide a + # name that is unique within the scope of your own + # queues. If you provide the name of an existing queue, a + # new queue isn't created and an error isn't + # returned. Instead, the request succeeds and the queue URL + # for the existing queue is returned. Exception: if you + # provide a value for +:default_visibility_timeout+ that is + # different from the value for the existing queue, you + # receive an error. + # + # @param [Hash] opts Additional options for creating the + # queue. + # + # @option opts [Integer] :default_visibility_timeout The + # visibility timeout (in seconds) to use for this queue. + # + # @return [Queue] The newly created queue. + # + def create(name, opts = {}) + resp = client.create_queue(opts.merge(:queue_name => name)) + Queue.new(resp.queue_url, :config => config) + end + + # @yieldparam [Queue] queue Each {Queue} object in the collection. + def each(&block) + client.list_queues.queue_urls.each do |url| + queue = self[url] + yield(queue) + end + end + + # @param [String] prefix The queue name prefix. + # @return [QueueCollection] A new collection representing only + # the queues whose names start with the given prefix. + def with_prefix(prefix) + self.class.new(:prefix => prefix, :config => config) + end + + # @return [Queue] The queue with the given URL. + def [](url) + Queue.new(url, :config => config) + end + + end + + end +end diff --git a/lib/aws/sqs/received_message.rb b/lib/aws/sqs/received_message.rb new file mode 100644 index 00000000000..f0119742524 --- /dev/null +++ b/lib/aws/sqs/received_message.rb @@ -0,0 +1,184 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' + +module AWS + class SQS + + # Represents a message received from an Amazon SQS Queue. + class ReceivedMessage + + include Model + + # @return [Queue] The queue from which this message was + # received. + attr_reader :queue + + # @return [String] The ID of the message. + attr_reader :id + + # @return [String] A string associated with this specific + # instance of receiving the message. + attr_reader :handle + + # @return [String] The message's contents. + attr_reader :body + + # @return [String] An MD5 digest of the message body. + attr_reader :md5 + + # @private + attr_reader :attributes + + # @private + ATTRIBUTE_ALIASES = { + :sent_at => :sent_timestamp, + :receive_count => :approximate_receive_count, + :first_received_at => :approximate_first_receive_timestamp + } + + # @private + def initialize(queue, id, handle, opts = {}) + @queue = queue + @id = id + @handle = handle + @body = opts[:body] + @md5 = opts[:md5] + @attributes = opts[:attributes] || {} + super + end + + # When SNS publishes messages to SQS queues the message body is + # formatted as a json message and then base 64 encoded. + # An easy way to work with SNS messages is to call this method: + # + # sns_msg = message.as_sns_message + # + # sns_msg.topic + # #=> + # + # sns_msg.to_h.inspect + # #=> { :body => '...', :topic_arn => ... } + # + # @return [ReceivedSNSMessage] + def as_sns_message + ReceivedSNSMessage.new(body, :config => config) + end + + # Deletes the message from the queue. Even if the message is + # locked by another reader due to the visibility timeout + # setting, it is still deleted from the queue. If you leave a + # message in the queue for more than 4 days, SQS automatically + # deletes it. + # + # If you use {Queue#poll} or {Queue#receive_message} in block + # form, the messages you receive will be deleted automatically + # unless an exception occurs while you are processing them. + # You can still use this method if you want to delete a + # message early and then continue processing it. + # + # @note It is possible you will receive a message even after + # you have deleted it. This might happen on rare occasions + # if one of the servers storing a copy of the message is + # unavailable when you request to delete the message. The + # copy remains on the server and might be returned to you + # again on a subsequent receive request. You should create + # your system to be idempotent so that receiving a + # particular message more than once is not a problem. + # + # @return [nil] + def delete + client.delete_message( + :queue_url => queue.url, + :receipt_handle => handle + ) + nil + end + + # Changes the visibility timeout of a specified message in a + # queue to a new value. The maximum allowed timeout value you + # can set the value to is 12 hours. This means you can't + # extend the timeout of a message in an existing queue to more + # than a total visibility timeout of 12 hours. (For more + # information visibility timeout, see {Visibility + # Timeout}[http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/IntroductionArticle.html#AboutVT] + # in the Amazon SQS Developer Guide.) + # + # For example, let's say the timeout for the queue is 30 + # seconds, and you receive a message. Once you're 20 seconds + # into the timeout for that message (i.e., you have 10 seconds + # left), you extend it by 60 seconds by calling this method + # with +timeout+ set to 60 seconds. You have then changed the + # remaining visibility timeout from 10 seconds to 60 seconds. + # + # @note If you attempt to set the timeout to an amount more + # than the maximum time left, Amazon SQS returns an + # error. It will not automatically recalculate and increase + # the timeout to the maximum time remaining. + # + # @note Unlike with a queue, when you change the visibility + # timeout for a specific message, that timeout value is + # applied immediately but is not saved in memory for that + # message. If you don't delete a message after it is + # received, the visibility timeout for the message the next + # time it is received reverts to the original timeout value, + # not the value you set with this method. + # + # @return Returns the timeout argument as passed. + # + def visibility_timeout=(timeout) + client.change_message_visibility( + :queue_url => queue.url, + :receipt_handle => handle, + :visibility_timeout => timeout + ) + timeout + end + + # @return [String] The AWS account number (or the IP address, + # if anonymous access is allowed) of the sender. + def sender_id + attributes["SenderId"] + end + + # @return [Time] The time when the message was sent. + def sent_timestamp + @sent_at ||= + (timestamp = attributes["SentTimestamp"] and + Time.at(timestamp.to_i / 1000.0)) || nil + rescue RangeError => e + p [timestamp, timestamp.to_i] + end + alias_method :sent_at, :sent_timestamp + + # @return [Integer] The number of times a message has been + # received but not deleted. + def approximate_receive_count + (count = attributes["ApproximateReceiveCount"] and + count.to_i) or nil + end + alias_method :receive_count, :approximate_receive_count + + # @return [Time] The time when the message was first received. + def approximate_first_receive_timestamp + @received_at ||= + (timestamp = attributes["ApproximateFirstReceiveTimestamp"] and + Time.at(timestamp.to_i / 1000.0)) || nil + end + alias_method :first_received_at, :approximate_first_receive_timestamp + + end + + end +end diff --git a/lib/aws/sqs/received_sns_message.rb b/lib/aws/sqs/received_sns_message.rb new file mode 100644 index 00000000000..dd6774f549c --- /dev/null +++ b/lib/aws/sqs/received_sns_message.rb @@ -0,0 +1,112 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/model' +require 'time' + +module AWS + class SQS + + # Represents message published from an {SNS::Topic} to an {SQS::Queue}. + class ReceivedSNSMessage + + include Model + + # @param [String] encoded_body The base64 encoded json string + # from a message published by SNS. + # @param [Hash] options + def initialize encoded_body, options = {} + @encoded_body = encoded_body + super + end + + # @return [String] Returns the Base64 encoded JSON hash as was + # published by SNS. See {#body} to get the decoded message + # or {#to_h} to get the decoded JSON hash as a ruby hash. + def encoded_body + @encoded_body + end + + # @return[String] Returns the decoded message as was published. + def body + to_h[:body] + end + + # @return [String] Returns the ARN for the topic that published this + # message. + def topic_arn + to_h[:topic_arn] + end + + # @return [SNS::Topic] Returns the topic that published this message. + def topic + SNS::Topic.new(topic_arn, :config => config) + end + + # @return [String] Returns the message type. + def message_type + to_h[:message_type] + end + + # @return [String] Returns the calculated signature for the message. + def signature + to_h[:signature] + end + + # @return [String] Returns the signature version. + def signature_version + to_h[:signature_version] + end + + # @return [Time] Returns the time the message was published at by SNS. + # by SNS. + def published_at + to_h[:published_at] + end + + # @return [String] Returns the unique id of the SNS message. + def message_id + to_h[:message_id] + end + + # @return [String] Returns the url for the signing cert. + def signing_cert_url + to_h[:signing_cert_url] + end + + # @return [String] Returns the url you can request to unsubscribe message + # from this queue. + def unsubscribe_url + to_h[:unsubscribe_url] + end + + # @return [Hash] Returns the decoded message as a hash. + def to_h + data = JSON.parse(Base64.decode64(@encoded_body)) + { + :body => data['Message'], + :topic_arn => data['TopicArn'], + :message_type => data['Type'], + :signature => data['Signature'], + :signature_version => data['SignatureVersion'], + :published_at => Time.parse(data['Timestamp']), + :message_id => data['MessageId'], + :signing_cert_url => data['SigningCertURL'], + :unsubscribe_url => data['UnsubscribeURL'], + } + end + + end + + end +end diff --git a/lib/aws/sqs/request.rb b/lib/aws/sqs/request.rb new file mode 100644 index 00000000000..dcb8122b991 --- /dev/null +++ b/lib/aws/sqs/request.rb @@ -0,0 +1,44 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/http/request' +require 'aws/authorize_v2' + +module AWS + class SQS + + # @private + class Request < AWS::Http::Request + + include AuthorizeV2 + + def path + full_url.path + end + + def host + full_url.host + end + + private + def full_url + if url_param = params.find { |p| p.name == "QueueUrl" } + URI.parse(url_param.value) + else + URI::HTTP.build(:host => @host, :path => '/') + end + end + + end + end +end diff --git a/lib/aws/xml_grammar.rb b/lib/aws/xml_grammar.rb new file mode 100644 index 00000000000..58f8b9919a9 --- /dev/null +++ b/lib/aws/xml_grammar.rb @@ -0,0 +1,923 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws/meta_utils' +require 'aws/inflection' +require 'rexml/document' + +begin + require 'nokogiri' +rescue LoadError => e +end + +module AWS + + # @private + class XmlGrammar + + # @private + class Context + + def initialize + @data = {} + end + + def id + @data[:id] + end + + def method_missing(m, *args) + key = m.to_sym + + return super unless @data.key?(key) + @data[key] + end + + def respond_to?(m) + @data.key?(m.to_sym) or super + end + + def inspect + methods = @data.keys + "" + end + + # this gets called a LOT during response parsing, and having + # it be a public method is the fastest way to call it. + # Strictly speaking it should be private. + # @private + def __set_data__(getter, value) + @data[getter.to_sym] = value + end + + end + + # @private + class CustomizationContext < Hash + + def initialize(element_name = nil) + self[:children] = {} + + if element_name + self[:name] = element_name + recompute_accessors + end + end + + def []=(name, value) + super + if respond_to?("changed_#{name}") + send("changed_#{name}", value) + end + end + + def changed_boolean(value) + recompute_accessors + end + + def changed_renamed(value) + recompute_accessors + end + + def deep_copy(hash = self) + hash.inject(hash.class.new) do |copy,(key,value)| + if value.is_a?(CustomizationContext) + copy[key] = value.deep_copy + elsif value.is_a?(Hash) + copy[key] = deep_copy(value) + else + copy[key] = value + end + copy + end + end + + private + def recompute_accessors + ruby_name = Inflection.ruby_name((self[:renamed] || + self[:name]).to_s) + self[:getter] = + if self[:boolean] && ruby_name != "value" + "#{ruby_name}?" + else + ruby_name + end + self[:setter] = "#{ruby_name}=" + end + + end + + # @private + class << self + + def parse xml, options = {} + + context = options[:context] || Context.new + + if defined? Nokogiri + parser = Parser.new(context, customizations) + parser.extend(NokogiriAdapter) + xml = "" if xml.empty? + Nokogiri::XML::SAX::Parser.new(parser).parse(xml.strip) + else + parser = Parser.new(context, customizations) + parser.extend(REXMLSaxParserAdapter) + REXML::Parsers::StreamParser.new(REXML::Source.new(xml), parser).parse + end + + context + + end + + def simulate context = nil + StubResponse.new(customizations, context) + end + + def customize config = nil, &block + grammar = Class.new(self) + grammar.customizations = customizations.deep_copy + grammar.config_eval(config) if config + grammar.module_eval(&block) if block_given? + grammar + end + + def element element_name, &block + eval_customization_context(element_name, &block) + end + + def add_method method_name, &block + format_value do |value| + MetaUtils.extend_method(value = super(value), method_name, &block) + value + end +# different strategey, slightly different behavior +# element(method_name.to_s) do +# format_value(&block) +# force +# end + end + + def ignore + @current[:ignored] = true + end + + def rename new_name + @current[:renamed] = new_name.to_s + end + + def force + @current[:forced] = true + end + + def collect_values + @current[:collected] = true + @current[:initial_collection] = lambda { [] } + @current[:add_to_collection] = + lambda { |ary, val| ary << val } + force + end + + def index(name, &block) + (@customizations[:index_names] ||= []) << name + @current[:indexed] = [name, block] + end + + def boolean_value + @current[:boolean] = true + format_value {|value| super(value) == 'true' } + end + # required by the hash configuration method + alias_method :boolean, :boolean_value + + TRANSLATE_DIGITS = ['0123456789'.freeze, ('X'*10).freeze] + EASY_FORMAT = "XXXX-XX-XXTXX:XX:XX.XXXZ".freeze + DATE_PUNCTUATION = ['-:.TZ'.freeze, (' '*5).freeze] + + def datetime_value + datetime_like_value(DateTime, :civil) + end + + def time_value + datetime_like_value(Time, :utc) + end + alias_method :timestamp, :time_value + + def datetime_like_value(klass, parts_constructor) + format_value do |value| + value = super(value) + if value and value.tr(*TRANSLATE_DIGITS) == EASY_FORMAT + + # it's way faster to parse this specific format manually + # vs. DateTime#parse, and this happens to be the format + # that AWS uses almost (??) everywhere. + + parts = value.tr(*DATE_PUNCTUATION). + chop.split.map { |elem| elem.to_i } + klass.send(parts_constructor, *parts) + elsif value + # fallback in case we have to handle another date format + klass.parse(value) + else + nil + end + end + end + + def integer_value + format_value do |value| + value = super(value) + value.nil? ? nil : value.to_i + end + end + # required by the hash configuration method + alias_method :integer, :integer_value + alias_method :long, :integer_value + + def float_value + format_value do |value| + value = super(value) + value.nil? ? nil : value.to_f + end + end + + alias_method :float, :float_value + + def symbol_value + format_value do |value| + value = super(value) + ['', nil].include?(value) ? nil : Inflection.ruby_name(value).to_sym + end + end + + def format_value &block + @current[:value_formatter] ||= ValueFormatter.new + @current[:value_formatter].extend_format_value(&block) + end + + def list child_element_name = nil, &block + if child_element_name + ignore + parent_element_name = @current_name + element(child_element_name) do + rename(parent_element_name) + collect_values + yield if block_given? + end + else + collect_values + end + end + + def map_entry(key, value) + collect_values + element(key) { rename :key } + element(value) { rename :value } + @current[:initial_collection] = lambda { {} } + @current[:add_to_collection] = lambda do |hash, entry| + hash[entry.key] = entry.value + end + end + + def map entry_name, key, value + parent_element_name = @current_name + ignore + element(entry_name) do + rename(parent_element_name) + map_entry(key, value) + end + end + + def wrapper method_name, options = {}, &blk + if block_given? + customizations = + eval_customization_context(method_name, + CustomizationContext.new(method_name), + &blk) + raise NotImplementedError.new("can't customize wrapped " + + "elements within the wrapper") unless + customizations[:children].empty? + @current[:wrapper_frames] ||= {} + @current[:wrapper_frames][method_name] = customizations + end + + (options[:for] || []).each do |element_name| + element element_name do + @current[:wrapper] ||= [] + @current[:wrapper] << method_name + end + end + end + + def construct_value &block + @current[:construct_value] = block + end + + def eql? other + self.customizations == other.customizations + end + + protected + def initial_customizations(element_name = nil) + CustomizationContext.new(element_name) + end + + protected + def eval_customization_context name, initial = nil, &block + current_name = @current_name + current = @current + parent = @parent + begin + @current_name = name + @parent = @current + initial ||= customizations_for(name) + @current = initial + yield if block_given? + ensure + @current_name = current_name + @current = current + @parent = parent + end + + # will be modified to include the customizations defined in + # the block + initial + end + + protected + def config_eval(config) + config.each do |item| + (type, identifier, args) = parse_config_item(item) + case type + when :method + validate_config_method(identifier) + validate_args(identifier, args) + send(identifier, *args) + when :element + element(identifier) do + config_eval(args) + end + end + end + end + + protected + def validate_args(identifier, args) + arity = method(identifier).arity + if args.length > 0 + raise "#{identifier} does not accept an argument" if + arity == 0 + else + raise "#{identifier} requires an argument" unless + arity == 0 || arity == -1 + end + end + + protected + def parse_config_item(item) + case item + when Symbol + [:method, item, []] + when Hash + (method, arg) = item.to_a.first + if method.kind_of?(Symbol) + [:method, method, [arg].flatten] + else + [:element, method, arg] + end + end + end + + protected + def validate_config_method(method) + allow_methods = %w( + rename attribute_name boolean integer long float list force + ignore collect_values symbol_value timestamp map_entry map + ) + unless allow_methods.include?(method.to_s) + raise "#{method} cannot be used in configuration" + end + end + + protected + def customizations + @customizations ||= CustomizationContext.new + end + + protected + def customizations_for element_name + if @parent + @parent[:children][element_name] ||= + CustomizationContext.new(element_name) + else + customizations[:children][element_name] ||= + CustomizationContext.new(element_name) + end + end + + protected + def customizations= customizations + @customizations = customizations + @current = customizations + end + + end + + # @private + class ValueFormatter + + def extend_format_value &block + MetaUtils.extend_method(self, :format_value, &block) + end + + def format_value value + value + end + + end + + # @private + class Parser + + def initialize context, customizations + @context = context + @customizations = customizations + end + + def start_element element_name, attrs + + if @frame + @frame = @frame.build_child_frame(element_name) + else + @frame = RootFrame.new(@context, @customizations) + end + + # consume attributes the same way we consume nested xml elements + attrs.each do |(attr_name, attr_value)| + attr_frame = @frame.build_child_frame(attr_name) + attr_frame.add_text(attr_value) + @frame.consume_child_frame(attr_frame) + end + + end + + def end_element name + @frame.close + if @frame.parent_frame + child_frame = @frame + parent_frame = @frame.parent_frame + parent_frame.consume_child_frame(child_frame) + end + @frame = @frame.parent_frame + end + + def characters chars + @frame.add_text(chars) if @frame + end + + end + + module REXMLSaxParserAdapter + + require 'rexml/streamlistener' + include REXML::StreamListener + + def tag_start(name, attrs) + start_element(name, attrs) + end + + def tag_end(name) + end_element(name) + end + + def text(chars) + characters(chars) + end + + end + + module NokogiriAdapter + + def xmldecl(*args); end + def start_document; end + def end_document; end + def start_element_namespace(name, attrs = [], prefix = nil, uri = nil, ns = []) + start_element(name, attrs.map { |att| [att.localname, att.value] }) + end + def end_element_namespace(name, prefix = nil, uri = nil) + end_element(name) + end + def error(*args); end + + end + + # @private + class Frame + + attr_reader :parent_frame + + attr_reader :root_frame + + attr_reader :element_name + + attr_accessor :customizations + + def initialize element_name, options = {} + + @element_name = element_name + @context = options[:context] + @parent_frame = options[:parent_frame] + @root_frame = options[:root_frame] + @wrapper_frames = {} + + if @parent_frame + @customizations = @parent_frame.customizations_for_child(element_name) + else + @customizations = options[:customizations] + @root_frame ||= self + end + + if @root_frame == self and + indexes = @customizations[:index_names] + indexes.each do |name| + if context.kind_of?(Context) + context.__set_data__(name, {}) + else + add_mutators(name) + context.send("#{name}=", {}) + end + end + end + + # we build and discard child frames here so we can know + # which children should always add a method to this + # frame's context (forced elements, like collected arrays) + @customizations[:children].keys.each do |child_element_name| + consume_initial_frame(build_child_frame(child_element_name)) + end + + if @customizations[:wrapper_frames] + @customizations[:wrapper_frames].keys.each do |method_name| + consume_initial_frame(wrapper_frame_for(method_name)) + end + end + + end + + def build_child_frame(child_element_name) + Frame.new(child_element_name, + :parent_frame => self, + :root_frame => root_frame) + end + + def consume_child_frame child_frame + + return if child_frame.ignored? + + if child_frame.wrapped? + child_frame.wrapper_methods.each do |method_name| + consume_in_wrapper(method_name, child_frame) + end + else + # forced child frames have already added mutators to this context + add_mutators_for(child_frame) unless child_frame.forced? + + if child_frame.collected? + child_frame.add_to_collection(context.send(child_frame.getter), + child_frame.value) + else + invoke_setter(child_frame, child_frame.value) + end + end + + end + + def close + if indexed = @customizations[:indexed] + (name, block) = indexed + key = block.call(context) + [key].flatten.each do |k| + index(name)[k] = context + end + end + end + + def add_text text + @text ||= '' + @text << text + end + + def value + @customizations[:value_formatter] ? + @customizations[:value_formatter].format_value(default_value) : + default_value + end + + def context + @context ||= (self.ignored? ? parent_frame.context : construct_context) + end + + def setter + @customizations[:setter] + end + + def getter + @customizations[:getter] + end + + def initial_collection + @customizations[:initial_collection].call + end + + def add_to_collection(collection, value) + @customizations[:add_to_collection].call(collection, value) + end + + def index(name) + return root_frame.index(name) unless root_frame == self + context.send(name) + end + + protected + def consume_initial_frame(child_frame) + if child_frame.forced? + add_mutators_for(child_frame) + if child_frame.collected? + invoke_setter(child_frame, child_frame.initial_collection) + else + # this allows nested forced elements to appear + invoke_setter(child_frame, child_frame.value) + end + end + end + + protected + def construct_context + if @customizations[:construct_value] + instance_eval(&@customizations[:construct_value]) + else + Context.new + end + end + + protected + def consume_in_wrapper method_name, child_frame + wrapper_frame = wrapper_frame_for(method_name) + add_mutators(method_name) + + # the wrapper consumes the unwrapped child + customizations = child_frame.customizations.merge(:wrapper => nil) + child_frame = child_frame.dup + child_frame.customizations = customizations + + wrapper_frame.consume_child_frame(child_frame) + consume_child_frame(wrapper_frame) + end + + protected + def wrapper_frame_for(method_name) + @wrapper_frames[method_name] ||= + Frame.new(method_name.to_s, + :customizations => wrapper_customizations(method_name)) + end + + protected + def wrapper_customizations(method_name) + customizations = CustomizationContext.new(method_name) + customizations[:children] = @customizations[:children] + if wrapper_frames = @customizations[:wrapper_frames] and + additional = wrapper_frames[method_name] + additional[:children] = @customizations[:children].merge(additional[:children]) if + additional[:children] + customizations.merge!(additional) + end + customizations + end + + protected + def invoke_setter(child_frame, value) + if context.kind_of?(Context) + context.__set_data__(child_frame.getter, value) + else + context.send(child_frame.setter, value) + end + end + + protected + def add_mutators_for child_frame + return if context.kind_of?(Context) + add_mutators(child_frame.ruby_name, + child_frame.setter, + child_frame.getter) + end + + protected + def add_mutators(variable_name, + setter = nil, + getter = nil) + return if context.kind_of?(Context) + variable_name = variable_name.to_s.gsub(/\?$/, '') + setter ||= "#{variable_name}=" + getter ||= variable_name + return if context.respond_to?(getter) && context.respond_to?(setter) + MetaUtils.extend_method(context, setter) do |val| + instance_variable_set("@#{variable_name}", val) + end + MetaUtils.extend_method(context, getter) do + instance_variable_get("@#{variable_name}") + end + end + + protected + def forced? + @customizations[:forced] + end + + protected + def ignored? + @customizations[:ignored] + end + + protected + def collected? + @customizations[:collected] + end + + protected + def wrapped? + @customizations[:wrapper] + end + + protected + def wrapper_methods + @customizations[:wrapper] + end + + protected + def default_value + if + # TODO : move this out of the default value method + @context and + @context.respond_to?(:encoding) and + @context.encoding == 'base64' + then + Base64.decode64(@text.strip) + else + @context || @text + end + end + + protected + def ruby_name + Inflection.ruby_name(@customizations[:renamed] || element_name) + end + + protected + def customizations_for_child child_element_name + @customizations[:children][child_element_name] || + CustomizationContext.new(child_element_name) + end + + protected + def initial_customizations(element_name = nil) + end + + end + + # @private + class RootFrame < Frame + + def initialize context, customizations + super('ROOT', :context => context, :customizations => customizations) + end + + end + + # @private + class StubResponse + + def initialize customizations, context = nil + @customizations = customizations + stub_methods(customizations, context || self) + end + + def inspect + methods = public_methods - Object.public_methods + "" + end + + # @private + private + def stub_methods customizations, context + add_wrappers_to_context(customizations, context) + add_child_elements_to_context(customizations, context) + add_indexes_to_context(customizations, context) + end + + # @private + private + def add_wrappers_to_context customizations, context + wrappers(customizations) do |wrapper_name,wrapper_customizations| + MetaUtils.extend_method(context, wrapper_name) do + StubResponse.new(wrapper_customizations) + end + end + end + + # @private + private + def add_child_elements_to_context customizations, context + without_wrapper(customizations) do |child_name,child_rules| + + ruby_name = Inflection.ruby_name(child_rules[:renamed] || child_name) + + # we stop at any collected elements + if child_rules[:collected] + MetaUtils.extend_method(context, ruby_name) { [] } + next + end + + if child_rules[:construct_value] + + MetaUtils.extend_method(context, ruby_name) do + child_rules[:construct_value].call + end + + elsif child_rules[:children].empty? # has no child elements + + unless child_rules[:ignored] + + method_name = child_rules[:boolean] ? "#{ruby_name}?" : ruby_name + + MetaUtils.extend_method(context, method_name) do + if child_rules[:value_formatter] + child_rules[:value_formatter].format_value('') + else + nil + end + end + end + + else # it has one or more child elements + + if child_rules[:ignored] + stub_methods(child_rules, context) + else + MetaUtils.extend_method(context, ruby_name) do + StubResponse.new(child_rules) + end + end + + end + + end + end + + # @private + def add_indexes_to_context(customizations, context) + if indexes = customizations[:index_names] + indexes.each do |index| + MetaUtils.extend_method(context, index) { {} } + end + end + end + + # @private + def wrappers customizations, &block + wrappers = {} + customizations[:children].each_pair do |child_name,child_rules| + if child_rules[:wrapper] + wrapper_name = child_rules[:wrapper].first + wrappers[wrapper_name] ||= { :children => {} } + wrappers[wrapper_name][:children][child_name] = child_rules.merge(:wrapper => nil) + end + end + + wrappers.each_pair do |wrapper_name, wrapper_customizations| + yield(wrapper_name, wrapper_customizations) + end + end + + # @private + private + def without_wrapper customizations, &block + customizations[:children].each_pair do |child_name,child_rules| + unless child_rules[:wrapper] + yield(child_name, child_rules) + end + end + end + + end + + end +end diff --git a/recipebook/.gitignore b/recipebook/.gitignore new file mode 100644 index 00000000000..b8e18e697d9 --- /dev/null +++ b/recipebook/.gitignore @@ -0,0 +1,3 @@ +/log +/tmp +config/aws.yml diff --git a/recipebook/Gemfile b/recipebook/Gemfile new file mode 100644 index 00000000000..8952ff60bf0 --- /dev/null +++ b/recipebook/Gemfile @@ -0,0 +1,17 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +source 'http://rubygems.org' + +gem 'rails', '~> 3.0.6' +gem 'aws-sdk', '~> 0.1.0' +gem 'haml', '~> 3.0.18' +gem "sqlite3-ruby", :require => "sqlite3" +gem "memory_test_fix" +gem "jquery-rails" +gem "rdiscount" + +group :development, :test do + gem "cucumber-rails" + gem "capybara" + gem "rspec-rails" +end diff --git a/recipebook/Gemfile.lock b/recipebook/Gemfile.lock new file mode 100644 index 00000000000..b66f262c209 --- /dev/null +++ b/recipebook/Gemfile.lock @@ -0,0 +1,151 @@ +GEM + remote: http://rubygems.org/ + specs: + abstract (1.0.0) + actionmailer (3.0.7) + actionpack (= 3.0.7) + mail (~> 2.2.15) + actionpack (3.0.7) + activemodel (= 3.0.7) + activesupport (= 3.0.7) + builder (~> 2.1.2) + erubis (~> 2.6.6) + i18n (~> 0.5.0) + rack (~> 1.2.1) + rack-mount (~> 0.6.14) + rack-test (~> 0.5.7) + tzinfo (~> 0.3.23) + activemodel (3.0.7) + activesupport (= 3.0.7) + builder (~> 2.1.2) + i18n (~> 0.5.0) + activerecord (3.0.7) + activemodel (= 3.0.7) + activesupport (= 3.0.7) + arel (~> 2.0.2) + tzinfo (~> 0.3.23) + activeresource (3.0.7) + activemodel (= 3.0.7) + activesupport (= 3.0.7) + activesupport (3.0.7) + arel (2.0.9) + aws-sdk (0.1.0) + httparty (~> 0.7) + json (~> 1.4) + nokogiri (~> 1.4.4) + uuidtools (~> 2.1) + builder (2.1.2) + capybara (0.4.1.2) + celerity (>= 0.7.9) + culerity (>= 0.2.4) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + selenium-webdriver (>= 0.0.27) + xpath (~> 0.1.3) + celerity (0.8.9) + childprocess (0.1.8) + ffi (~> 1.0.6) + crack (0.1.8) + cucumber (0.10.2) + builder (>= 2.1.2) + diff-lcs (>= 1.1.2) + gherkin (>= 2.3.5) + json (>= 1.4.6) + term-ansicolor (>= 1.0.5) + cucumber-rails (0.4.1) + cucumber (>= 0.10.1) + nokogiri (>= 1.4.4) + rack-test (>= 0.5.7) + culerity (0.2.15) + diff-lcs (1.1.2) + erubis (2.6.6) + abstract (>= 1.0.0) + ffi (1.0.7) + rake (>= 0.8.7) + gherkin (2.3.6) + json (>= 1.4.6) + haml (3.0.25) + httparty (0.7.8) + crack (= 0.1.8) + i18n (0.5.0) + jquery-rails (0.2.7) + rails (~> 3.0) + thor (~> 0.14.4) + json (1.4.6) + json_pure (1.5.1) + mail (2.2.19) + activesupport (>= 2.3.6) + i18n (>= 0.4.0) + mime-types (~> 1.16) + treetop (~> 1.4.8) + memory_test_fix (0.2.0) + mime-types (1.16) + nokogiri (1.4.4) + polyglot (0.3.1) + rack (1.2.2) + rack-mount (0.6.14) + rack (>= 1.0.0) + rack-test (0.5.7) + rack (>= 1.0) + rails (3.0.7) + actionmailer (= 3.0.7) + actionpack (= 3.0.7) + activerecord (= 3.0.7) + activeresource (= 3.0.7) + activesupport (= 3.0.7) + bundler (~> 1.0) + railties (= 3.0.7) + railties (3.0.7) + actionpack (= 3.0.7) + activesupport (= 3.0.7) + rake (>= 0.8.7) + thor (~> 0.14.4) + rake (0.8.7) + rdiscount (1.6.8) + rspec (2.5.0) + rspec-core (~> 2.5.0) + rspec-expectations (~> 2.5.0) + rspec-mocks (~> 2.5.0) + rspec-core (2.5.1) + rspec-expectations (2.5.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.5.0) + rspec-rails (2.5.0) + actionpack (~> 3.0) + activesupport (~> 3.0) + railties (~> 3.0) + rspec (~> 2.5.0) + rubyzip (0.9.4) + selenium-webdriver (0.2.0) + childprocess (>= 0.1.7) + ffi (>= 1.0.7) + json_pure + rubyzip + sqlite3 (1.3.3) + sqlite3-ruby (1.3.3) + sqlite3 (>= 1.3.3) + term-ansicolor (1.0.5) + thor (0.14.6) + treetop (1.4.9) + polyglot (>= 0.3.1) + tzinfo (0.3.27) + uuidtools (2.1.2) + xpath (0.1.4) + nokogiri (~> 1.3) + +PLATFORMS + ruby + +DEPENDENCIES + aws-sdk (~> 0.1.0) + capybara + cucumber-rails + haml (~> 3.0.18) + jquery-rails + memory_test_fix + rails (~> 3.0.6) + rdiscount + rspec-rails + sqlite3-ruby diff --git a/recipebook/README b/recipebook/README new file mode 100644 index 00000000000..da8cfae4dda --- /dev/null +++ b/recipebook/README @@ -0,0 +1,20 @@ +== Recipe Book Sample Application == + +This application demonstrates some of the features of the AWS SDK for Ruby. To +run it, you must put your AWS credentials in a file named aws.yml in +the RAILS_ROOT/config directory. For example: + + access_key_id: MY_ACCESS_KEY + secret_access_key: MY_SECRET_KEY + +Once this file is in place, you can use the rails console to create the +SimpleDB domain that the application will use: + + $ rails console + > Recipe.create_domain + +Then you can run the application locally: + + $ rails server + +Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/recipebook/Rakefile b/recipebook/Rakefile new file mode 100644 index 00000000000..2af1ab6956d --- /dev/null +++ b/recipebook/Rakefile @@ -0,0 +1,20 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require File.expand_path('../config/application', __FILE__) +require 'rake' + +Recipebook::Application.load_tasks diff --git a/recipebook/app/controllers/application_controller.rb b/recipebook/app/controllers/application_controller.rb new file mode 100644 index 00000000000..b3682960714 --- /dev/null +++ b/recipebook/app/controllers/application_controller.rb @@ -0,0 +1,26 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +class ApplicationController < ActionController::Base + protect_from_forgery + + before_filter :initialize_remote_user + + def initialize_remote_user + if request.env['REMOTE_USER'] + @remote_user = request.env['REMOTE_USER'] + else + @remote_user = "Anonymous" + end + end +end diff --git a/recipebook/app/controllers/index_controller.rb b/recipebook/app/controllers/index_controller.rb new file mode 100644 index 00000000000..fb5218186cb --- /dev/null +++ b/recipebook/app/controllers/index_controller.rb @@ -0,0 +1,22 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +class IndexController < ApplicationController + before_filter :initialize_remote_user + + # See your routes.rb to change this and remove this controller + def index + redirect_to recipes_path + end + +end diff --git a/recipebook/app/controllers/recipes_controller.rb b/recipebook/app/controllers/recipes_controller.rb new file mode 100644 index 00000000000..e0b92c17055 --- /dev/null +++ b/recipebook/app/controllers/recipes_controller.rb @@ -0,0 +1,61 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +class RecipesController < ApplicationController + + def index + @recipes = Recipe.find(:all) + end + + def show + @recipe = Recipe[params[:id]] + end + + def new + @recipe = Recipe.new + end + + def create + @recipe = Recipe.new(params[:recipe]) + @recipe.author = @remote_user + if @recipe.save + flash[:notice] = 'Recipe added.' + redirect_to recipe_path(@recipe) + else + flash.now[:error] = 'Unable to add recipe, see errors below.' + render :action => 'new' + end + end + + def edit + @recipe = Recipe.by(@remote_user).find(params[:id]) + end + + def update + @recipe = Recipe[params[:id]] + if @recipe.update_attributes(params[:recipe]) + flash[:notice] = 'Recipe updated.' + redirect_to recipe_path(@recipe) + else + flash.now[:error] = 'Unable to update recipe, see errors below.' + render :action => 'edit' + end + end + + def destroy + Recipe.by(@remote_user).find(params[:id]).delete + flash[:notice] = 'Recipe deleted.' + redirect_to :action => 'index' + end + +end diff --git a/recipebook/app/helpers/application_helper.rb b/recipebook/app/helpers/application_helper.rb new file mode 100644 index 00000000000..82932ff079f --- /dev/null +++ b/recipebook/app/helpers/application_helper.rb @@ -0,0 +1,31 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module ApplicationHelper + + def title str + content_for(:title, str) + content_tag('h1', str) + end + + def flash_messages + messages = [] + [:error, :warning, :notice, :info].each do |level| + if flash[level] + messages << content_tag('p', raw(flash[level]), :class => level) + end + end + messages.empty? ? nil : content_tag('div', raw(messages), :id => 'flashes') + end + +end diff --git a/recipebook/app/helpers/recipes_helper.rb b/recipebook/app/helpers/recipes_helper.rb new file mode 100644 index 00000000000..11c3f879b73 --- /dev/null +++ b/recipebook/app/helpers/recipes_helper.rb @@ -0,0 +1,15 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module RecipesHelper +end diff --git a/recipebook/app/models/recipe.rb b/recipebook/app/models/recipe.rb new file mode 100644 index 00000000000..d46fc115796 --- /dev/null +++ b/recipebook/app/models/recipe.rb @@ -0,0 +1,33 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +class Recipe < AWS::Record::Base + + set_domain_name "#{Rails.env}_recipe_book" + + ## attributes + + string_attr :title + string_attr :author + string_attr :directions + string_attr :ingredients, :set => true + + ## validations + + validates_presence_of :title, :author + + ## scopes + + scope(:by) {|username| where(:author => username) } + +end diff --git a/recipebook/app/views/index/index.rhtml b/recipebook/app/views/index/index.rhtml new file mode 100644 index 00000000000..da230a85a88 --- /dev/null +++ b/recipebook/app/views/index/index.rhtml @@ -0,0 +1,6 @@ + +Recipe Book! + + + <%= @domains.map(&:name).join(", ") %> + diff --git a/recipebook/app/views/layouts/application.html.erb b/recipebook/app/views/layouts/application.html.erb new file mode 100644 index 00000000000..a9eff19672d --- /dev/null +++ b/recipebook/app/views/layouts/application.html.erb @@ -0,0 +1,14 @@ + + + + Recipebook + <%= stylesheet_link_tag :all %> + <%= javascript_include_tag :defaults %> + <%= csrf_meta_tag %> + + + +<%= yield %> + + + diff --git a/recipebook/app/views/layouts/application.html.haml b/recipebook/app/views/layouts/application.html.haml new file mode 100644 index 00000000000..e60b4a447c2 --- /dev/null +++ b/recipebook/app/views/layouts/application.html.haml @@ -0,0 +1,23 @@ +-# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +-# +-# Licensed under the Apache License, Version 2.0 (the "License"). You +-# may not use this file except in compliance with the License. A copy of +-# the License is located at +-# +-# http://aws.amazon.com/apache2.0/ +-# +-# or in the "license" file accompanying this file. This file is +-# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +-# ANY KIND, either express or implied. See the License for the specific +-# language governing permissions and limitations under the License. + +%html + %head + %title= yield(:title) + ' : Ruby Recipe Book' + = stylesheet_link_tag :all + = javascript_include_tag :defaults + = csrf_meta_tag + %body + #content + = flash_messages + = yield diff --git a/recipebook/app/views/recipes/_form.html.haml b/recipebook/app/views/recipes/_form.html.haml new file mode 100644 index 00000000000..d12a429c3b6 --- /dev/null +++ b/recipebook/app/views/recipes/_form.html.haml @@ -0,0 +1,25 @@ +-# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +-# +-# Licensed under the Apache License, Version 2.0 (the "License"). You +-# may not use this file except in compliance with the License. A copy of +-# the License is located at +-# +-# http://aws.amazon.com/apache2.0/ +-# +-# or in the "license" file accompanying this file. This file is +-# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +-# ANY KIND, either express or implied. See the License for the specific +-# language governing permissions and limitations under the License. + += wow_form_for @recipe do |form| + = form.text_field :title + = form.text_area :directions + .row + %label.row_label Ingredients + %ul#ingredient_list + - @recipe.ingredients.each do |ingredient| + %li + = text_field_tag "recipe[ingredients][]", ingredient + %a.remove_ingredient(href="#") Remove + %a.add_ingredient(href="#") Add + = form.submit diff --git a/recipebook/app/views/recipes/edit.html.haml b/recipebook/app/views/recipes/edit.html.haml new file mode 100644 index 00000000000..61add11cdd6 --- /dev/null +++ b/recipebook/app/views/recipes/edit.html.haml @@ -0,0 +1,15 @@ +-# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +-# +-# Licensed under the Apache License, Version 2.0 (the "License"). You +-# may not use this file except in compliance with the License. A copy of +-# the License is located at +-# +-# http://aws.amazon.com/apache2.0/ +-# +-# or in the "license" file accompanying this file. This file is +-# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +-# ANY KIND, either express or implied. See the License for the specific +-# language governing permissions and limitations under the License. + += title('Edit Recipe') += render "form", :recipe => @recipe diff --git a/recipebook/app/views/recipes/index.html.haml b/recipebook/app/views/recipes/index.html.haml new file mode 100644 index 00000000000..44c8c241282 --- /dev/null +++ b/recipebook/app/views/recipes/index.html.haml @@ -0,0 +1,22 @@ +-# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +-# +-# Licensed under the Apache License, Version 2.0 (the "License"). You +-# may not use this file except in compliance with the License. A copy of +-# the License is located at +-# +-# http://aws.amazon.com/apache2.0/ +-# +-# or in the "license" file accompanying this file. This file is +-# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +-# ANY KIND, either express or implied. See the License for the specific +-# language governing permissions and limitations under the License. + += title('All Recipes') +%ul + - @recipes.each do |recipe| + %li + = link_to(recipe.title, recipe) + - unless_blank(recipe.author) do |author| + by #{author} + +%p= link_to "Add a Recipe", new_recipe_path diff --git a/recipebook/app/views/recipes/new.html.haml b/recipebook/app/views/recipes/new.html.haml new file mode 100644 index 00000000000..97da9b421dc --- /dev/null +++ b/recipebook/app/views/recipes/new.html.haml @@ -0,0 +1,15 @@ +-# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +-# +-# Licensed under the Apache License, Version 2.0 (the "License"). You +-# may not use this file except in compliance with the License. A copy of +-# the License is located at +-# +-# http://aws.amazon.com/apache2.0/ +-# +-# or in the "license" file accompanying this file. This file is +-# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +-# ANY KIND, either express or implied. See the License for the specific +-# language governing permissions and limitations under the License. + += title('Add a Recipe') += render "form", :recipe => @recipe diff --git a/recipebook/app/views/recipes/show.html.haml b/recipebook/app/views/recipes/show.html.haml new file mode 100644 index 00000000000..30dae142396 --- /dev/null +++ b/recipebook/app/views/recipes/show.html.haml @@ -0,0 +1,33 @@ +-# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +-# +-# Licensed under the Apache License, Version 2.0 (the "License"). You +-# may not use this file except in compliance with the License. A copy of +-# the License is located at +-# +-# http://aws.amazon.com/apache2.0/ +-# +-# or in the "license" file accompanying this file. This file is +-# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +-# ANY KIND, either express or implied. See the License for the specific +-# language governing permissions and limitations under the License. + +#links + %ul + %li= link_to "All Recipes", recipes_path + - if @recipe.author == @remote_user + %li= link_to "Edit Recipe", edit_recipe_path(@recipe) + %li= link_to "Delete Recipe", recipe_path(@recipe), :method => 'delete' + += title @recipe.title +- unless @recipe.author.blank? + %h2 by #{@recipe.author} + +- unless_blank @recipe.ingredients do |ingredients| + %h3 Ingredients + %ul + - ingredients.each do |ingredient| + %li= ingredient + +- unless_blank @recipe.directions do |directions| + %h3 Directions + %p= directions diff --git a/recipebook/config.ru b/recipebook/config.ru new file mode 100644 index 00000000000..f7add210ef4 --- /dev/null +++ b/recipebook/config.ru @@ -0,0 +1,4 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run Recipebook::Application diff --git a/recipebook/config/application.rb b/recipebook/config/application.rb new file mode 100644 index 00000000000..7251c44d528 --- /dev/null +++ b/recipebook/config/application.rb @@ -0,0 +1,55 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require File.expand_path('../boot', __FILE__) + +require 'rails/all' + +# If you have a Gemfile, require the gems listed there, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(:default, Rails.env) if defined?(Bundler) + +module Recipebook + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Custom directories with classes and modules you want to be autoloadable. + # config.autoload_paths += %W(#{config.root}/extras) + + # Only load the plugins named here, in the order given (default is alphabetical). + # :all can be used as a placeholder for all plugins not explicitly named. + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + + # Activate observers that should always be running. + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de + + # JavaScript files you want as :defaults (application.js is always included). + # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) + + # Configure the default encoding used in templates for Ruby 1.9. + config.encoding = "utf-8" + + # Configure sensitive parameters which will be filtered from the log file. + config.filter_parameters += [:password] + end +end diff --git a/recipebook/config/boot.rb b/recipebook/config/boot.rb new file mode 100644 index 00000000000..966354de48a --- /dev/null +++ b/recipebook/config/boot.rb @@ -0,0 +1,19 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'rubygems' + +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + +require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) diff --git a/recipebook/config/cucumber.yml b/recipebook/config/cucumber.yml new file mode 100644 index 00000000000..596e2470194 --- /dev/null +++ b/recipebook/config/cucumber.yml @@ -0,0 +1,21 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +<% +rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" +rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" +std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} --strict --tags ~@wip" +%> +default: <%= std_opts %> features +wip: --tags @wip:3 --wip features +rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip diff --git a/recipebook/config/database.yml b/recipebook/config/database.yml new file mode 100644 index 00000000000..0b8b102c0fc --- /dev/null +++ b/recipebook/config/database.yml @@ -0,0 +1,38 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# SQLite version 3.x +# gem install sqlite3-ruby (not necessary on OS X Leopard) +development: + adapter: sqlite3 + database: ":memory:" + pool: 5 + timeout: 5000 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: &test + adapter: sqlite3 + database: ":memory:" + pool: 5 + timeout: 5000 + +production: + adapter: sqlite3 + database: ":memory:" + pool: 5 + timeout: 5000 + +cucumber: + <<: *test diff --git a/recipebook/config/environment.rb b/recipebook/config/environment.rb new file mode 100644 index 00000000000..0288ed994cf --- /dev/null +++ b/recipebook/config/environment.rb @@ -0,0 +1,18 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# Load the rails application +require File.expand_path('../application', __FILE__) + +# Initialize the rails application +Recipebook::Application.initialize! diff --git a/recipebook/config/environments/development.rb b/recipebook/config/environments/development.rb new file mode 100644 index 00000000000..399de8edf53 --- /dev/null +++ b/recipebook/config/environments/development.rb @@ -0,0 +1,38 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Recipebook::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the webserver when you make code changes. + config.cache_classes = false + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_view.debug_rjs = true + config.action_controller.perform_caching = false + + # Don't care if the mailer can't send + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger + config.active_support.deprecation = :log + + # Only use best-standards-support built into browsers + config.action_dispatch.best_standards_support = :builtin +end diff --git a/recipebook/config/environments/production.rb b/recipebook/config/environments/production.rb new file mode 100644 index 00000000000..3dd298ac46b --- /dev/null +++ b/recipebook/config/environments/production.rb @@ -0,0 +1,62 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Recipebook::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # The production environment is meant for finished, "live" apps. + # Code is not reloaded between requests + config.cache_classes = true + + # Full error reports are disabled and caching is turned on + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Specifies the header that your server uses for sending files + config.action_dispatch.x_sendfile_header = "X-Sendfile" + + # For nginx: + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' + + # If you have no front-end server that supports something like X-Sendfile, + # just comment this out and Rails will serve the files + + # See everything in the log (default is :info) + # config.log_level = :debug + + # Use a different logger for distributed setups + # config.logger = SyslogLogger.new + + # Use a different cache store in production + # config.cache_store = :mem_cache_store + + # Disable Rails's static asset server + # In production, Apache or nginx will already do this + config.serve_static_assets = false + + # Enable serving of images, stylesheets, and javascripts from an asset server + # config.action_controller.asset_host = "http://assets.example.com" + + # Disable delivery errors, bad email addresses will be ignored + # config.action_mailer.raise_delivery_errors = false + + # Enable threaded mode + # config.threadsafe! + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation can not be found) + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners + config.active_support.deprecation = :notify +end diff --git a/recipebook/config/environments/test.rb b/recipebook/config/environments/test.rb new file mode 100644 index 00000000000..d02faf84a04 --- /dev/null +++ b/recipebook/config/environments/test.rb @@ -0,0 +1,48 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Recipebook::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment + config.action_controller.allow_forgery_protection = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Print deprecation notices to the stderr + config.active_support.deprecation = :stderr +end diff --git a/recipebook/config/initializers/aws.rb b/recipebook/config/initializers/aws.rb new file mode 100644 index 00000000000..b83ce674358 --- /dev/null +++ b/recipebook/config/initializers/aws.rb @@ -0,0 +1,18 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +require 'aws' + +include AWS +config_path = File.expand_path(File.dirname(__FILE__)+"/../aws.yml") +AWS.config(YAML.load(File.read(config_path))) diff --git a/recipebook/config/initializers/backtrace_silencers.rb b/recipebook/config/initializers/backtrace_silencers.rb new file mode 100644 index 00000000000..73e166ccb59 --- /dev/null +++ b/recipebook/config/initializers/backtrace_silencers.rb @@ -0,0 +1,20 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/recipebook/config/initializers/form_wow.rb b/recipebook/config/initializers/form_wow.rb new file mode 100644 index 00000000000..9eeca03cdaa --- /dev/null +++ b/recipebook/config/initializers/form_wow.rb @@ -0,0 +1,15 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +FormWow.nuke_field_with_errors +ActionView::Base.send(:include, FormWow::Helpers) diff --git a/recipebook/config/initializers/inflections.rb b/recipebook/config/initializers/inflections.rb new file mode 100644 index 00000000000..d8d809c38b5 --- /dev/null +++ b/recipebook/config/initializers/inflections.rb @@ -0,0 +1,23 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format +# (all these examples are active by default): +# ActiveSupport::Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end diff --git a/recipebook/config/initializers/mime_types.rb b/recipebook/config/initializers/mime_types.rb new file mode 100644 index 00000000000..9efaa33fc30 --- /dev/null +++ b/recipebook/config/initializers/mime_types.rb @@ -0,0 +1,18 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register_alias "text/html", :iphone diff --git a/recipebook/config/initializers/monkey_patch.rb b/recipebook/config/initializers/monkey_patch.rb new file mode 100644 index 00000000000..84c7fea517b --- /dev/null +++ b/recipebook/config/initializers/monkey_patch.rb @@ -0,0 +1,19 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +def unless_blank value, &block + unless value.blank? + return yield(value) + end + nil +end diff --git a/recipebook/config/initializers/secret_token.rb b/recipebook/config/initializers/secret_token.rb new file mode 100644 index 00000000000..2f980f1e460 --- /dev/null +++ b/recipebook/config/initializers/secret_token.rb @@ -0,0 +1,20 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# Be sure to restart your server when you modify this file. + +# Your secret key for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +Recipebook::Application.config.secret_token = '2a66992842130bc385e6d19c3606807024417d6489552f99eedef9c22721fbf912dac6d125f4d70c2cb7113b7e664cad897485ff766271d84e3dd2666d3914af' diff --git a/recipebook/config/initializers/session_store.rb b/recipebook/config/initializers/session_store.rb new file mode 100644 index 00000000000..3437c45425c --- /dev/null +++ b/recipebook/config/initializers/session_store.rb @@ -0,0 +1,21 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# Be sure to restart your server when you modify this file. + +Recipebook::Application.config.session_store :cookie_store, :key => '_recipebook_session' + +# Use the database for sessions instead of the cookie-based default, +# which shouldn't be used to store highly confidential information +# (create the session table with "rails generate session_migration") +# Recipebook::Application.config.session_store :active_record_store diff --git a/recipebook/config/locales/en.yml b/recipebook/config/locales/en.yml new file mode 100644 index 00000000000..feb9e6940f1 --- /dev/null +++ b/recipebook/config/locales/en.yml @@ -0,0 +1,18 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# Sample localization file for English. Add more files in this directory for other locales. +# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + hello: "Hello world" diff --git a/recipebook/config/routes.rb b/recipebook/config/routes.rb new file mode 100644 index 00000000000..c999f5d99bb --- /dev/null +++ b/recipebook/config/routes.rb @@ -0,0 +1,76 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Recipebook::Application.routes.draw do + # Connect '/' to the index controller + root :to => "index#index" + + resources :recipes + + # The priority is based upon order of creation: + # first created -> highest priority. + + # Sample of regular route: + # match 'products/:id' => 'catalog#view' + # Keep in mind you can assign values other than :controller and :action + + # Sample of named route: + # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase + # This route can be invoked with purchase_url(:id => product.id) + + # Sample resource route (maps HTTP verbs to controller actions automatically): + # resources :products + + # Sample resource route with options: + # resources :products do + # member do + # get 'short' + # post 'toggle' + # end + # + # collection do + # get 'sold' + # end + # end + + # Sample resource route with sub-resources: + # resources :products do + # resources :comments, :sales + # resource :seller + # end + + # Sample resource route with more complex sub-resources + # resources :products do + # resources :comments + # resources :sales do + # get 'recent', :on => :collection + # end + # end + + # Sample resource route within a namespace: + # namespace :admin do + # # Directs /admin/products/* to Admin::ProductsController + # # (app/controllers/admin/products_controller.rb) + # resources :products + # end + + # You can have the root of your site routed with "root" + # just remember to delete public/index.html. + # root :to => "welcome#index" + + # See how all your routes lay out with "rake routes" + + # This is a legacy wild controller route that's not recommended for RESTful applications. + # Note: This route will make all actions in every controller accessible via GET requests. + # match ':controller(/:action(/:id(.:format)))' +end diff --git a/recipebook/db/schema.rb b/recipebook/db/schema.rb new file mode 100644 index 00000000000..9bcfefb5f65 --- /dev/null +++ b/recipebook/db/schema.rb @@ -0,0 +1,15 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# This is a placeholder to allow unit tests to succeed. +# Use rake db:schema:dump to replace this with your actual schema. diff --git a/recipebook/db/seeds.rb b/recipebook/db/seeds.rb new file mode 100644 index 00000000000..ed3ff2e92c5 --- /dev/null +++ b/recipebook/db/seeds.rb @@ -0,0 +1,20 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# +# Examples: +# +# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) +# Mayor.create(:name => 'Daley', :city => cities.first) diff --git a/recipebook/features/recipes/crud.feature b/recipebook/features/recipes/crud.feature new file mode 100644 index 00000000000..9376f852c21 --- /dev/null +++ b/recipebook/features/recipes/crud.feature @@ -0,0 +1,58 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +Feature: CRUD Recipes + + As someone with recipes to share + I want to post my recipes + So that others can use them + + Scenario: Create a recipe + Given I am logged in as "fred" + And I am on the new recipe page + When I fill in the following: + | Title | Cheesy Grits | + | Directions | Cook it. | + And I press "Create Recipe" + Then I should see the following text: + | h1 | Your recipe: | + | h2.recipe_title | Cheesy Grits by fred | + | p.recipe_directions | Cook it. | + + Scenario: List recipes + Given I am logged in as "fred" + And I create a recipe named "Beef Stew" + When I go to the recipes page + Then I should see the following text: + | li | Beef Stew by fred | + + Scenario: Recipe with ingredients + Given I am on the new recipe page + When I fill in the following: + | Title | Sliced Pears | + | Directions | Serve. | + And I click the "Add Ingredient" link + And I fill in "Ingredients" with "1c pears, sliced" + And I press "Create Recipe" + Then I should see the following text: + | h3.recipe_ingredients | Ingredients: | + | li.ingredient | 1c pears, sliced | + + @wip + Scenario: Edit recipe preserves saved values + Given I create a recipe named "Beef Stew" with the ingredient "beef" + And I am on the recipe page for "Beef Stew" + When I follow "Edit Recipe" + Then the field "Title" should contain "Beef Stew" + And the ingredients list should contain "beef" diff --git a/recipebook/features/recipes/step_definitions/crud.rb b/recipebook/features/recipes/step_definitions/crud.rb new file mode 100644 index 00000000000..797a386d566 --- /dev/null +++ b/recipebook/features/recipes/step_definitions/crud.rb @@ -0,0 +1,22 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Given /^I create a recipe named "([^\"]*)"$/ do |title| + visit new_recipe_path + Given %{I fill in "Title" with "#{title}"} + click_button("Create Recipe") +end + +Given /^I create a recipe named "([^\"]*)" with the ingredient "([^\"]*)"$/ do |title, ingredient| + pending +end diff --git a/recipebook/features/step_definitions/more_web_steps.rb b/recipebook/features/step_definitions/more_web_steps.rb new file mode 100644 index 00000000000..f79afc17dd2 --- /dev/null +++ b/recipebook/features/step_definitions/more_web_steps.rb @@ -0,0 +1,33 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +Given /^I am logged in as "([^\"]*)"$/ do |user| + page.driver.extend(Module.new do + define_method(:env) do + super.merge("REMOTE_USER" => user) + end + end) +end + +Then /^I should see the following text:$/ do |table| + #pp page.all(:xpath, "//*").map {|e| [e.inspect, e.text]} + table.rows_hash.each do |selector, text| + page.should have_selector(selector) + find(selector).should have_content(text) + end +end + +When /^I click the "([^\"]*)" link$/ do |name| + pending # doesn't seem to work for JS links + When %{I follow "#{name}"} +end diff --git a/recipebook/features/step_definitions/web_steps.rb b/recipebook/features/step_definitions/web_steps.rb new file mode 100644 index 00000000000..9a2f75f3893 --- /dev/null +++ b/recipebook/features/step_definitions/web_steps.rb @@ -0,0 +1,232 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +require 'uri' +require 'cgi' +require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths")) + +module WithinHelpers + def with_scope(locator) + locator ? within(locator) { yield } : yield + end +end +World(WithinHelpers) + +Given /^(?:|I )am on (.+)$/ do |page_name| + visit path_to(page_name) +end + +When /^(?:|I )go to (.+)$/ do |page_name| + visit path_to(page_name) +end + +When /^(?:|I )press "([^"]*)"(?: within "([^"]*)")?$/ do |button, selector| + with_scope(selector) do + click_button(button) + end +end + +When /^(?:|I )follow "([^"]*)"(?: within "([^"]*)")?$/ do |link, selector| + with_scope(selector) do + click_link(link) + end +end + +When /^(?:|I )fill in "([^"]*)" with "([^"]*)"(?: within "([^"]*)")?$/ do |field, value, selector| + with_scope(selector) do + fill_in(field, :with => value) + end +end + +When /^(?:|I )fill in "([^"]*)" for "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector| + with_scope(selector) do + fill_in(field, :with => value) + end +end + +# Use this to fill in an entire form with data from a table. Example: +# +# When I fill in the following: +# | Account Number | 5002 | +# | Expiry date | 2009-11-01 | +# | Note | Nice guy | +# | Wants Email? | | +# +# TODO: Add support for checkbox, select og option +# based on naming conventions. +# +When /^(?:|I )fill in the following(?: within "([^"]*)")?:$/ do |selector, fields| + with_scope(selector) do + fields.rows_hash.each do |name, value| + When %{I fill in "#{name}" with "#{value}"} + end + end +end + +When /^(?:|I )select "([^"]*)" from "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector| + with_scope(selector) do + select(value, :from => field) + end +end + +When /^(?:|I )check "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector| + with_scope(selector) do + check(field) + end +end + +When /^(?:|I )uncheck "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector| + with_scope(selector) do + uncheck(field) + end +end + +When /^(?:|I )choose "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector| + with_scope(selector) do + choose(field) + end +end + +When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"(?: within "([^"]*)")?$/ do |path, field, selector| + with_scope(selector) do + attach_file(field, path) + end +end + +Then /^(?:|I )should see JSON:$/ do |expected_json| + require 'json' + expected = JSON.pretty_generate(JSON.parse(expected_json)) + actual = JSON.pretty_generate(JSON.parse(response.body)) + expected.should == actual +end + +Then /^(?:|I )should see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector| + with_scope(selector) do + if page.respond_to? :should + page.should have_content(text) + else + assert page.has_content?(text) + end + end +end + +Then /^(?:|I )should see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector| + regexp = Regexp.new(regexp) + with_scope(selector) do + if page.respond_to? :should + page.should have_xpath('//*', :text => regexp) + else + assert page.has_xpath?('//*', :text => regexp) + end + end +end + +Then /^(?:|I )should not see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector| + with_scope(selector) do + if page.respond_to? :should + page.should have_no_content(text) + else + assert page.has_no_content?(text) + end + end +end + +Then /^(?:|I )should not see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector| + regexp = Regexp.new(regexp) + with_scope(selector) do + if page.respond_to? :should + page.should have_no_xpath('//*', :text => regexp) + else + assert page.has_no_xpath?('//*', :text => regexp) + end + end +end + +Then /^the "([^"]*)" field(?: within "([^"]*)")? should contain "([^"]*)"$/ do |field, selector, value| + with_scope(selector) do + field = find_field(field) + field_value = (field.tag_name == 'textarea') ? field.text : field.value + if field_value.respond_to? :should + field_value.should =~ /#{value}/ + else + assert_match(/#{value}/, field_value) + end + end +end + +Then /^the "([^"]*)" field(?: within "([^"]*)")? should not contain "([^"]*)"$/ do |field, selector, value| + with_scope(selector) do + field = find_field(field) + field_value = (field.tag_name == 'textarea') ? field.text : field.value + if field_value.respond_to? :should_not + field_value.should_not =~ /#{value}/ + else + assert_no_match(/#{value}/, field_value) + end + end +end + +Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should be checked$/ do |label, selector| + with_scope(selector) do + field_checked = find_field(label)['checked'] + if field_checked.respond_to? :should + field_checked.should be_true + else + assert field_checked + end + end +end + +Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should not be checked$/ do |label, selector| + with_scope(selector) do + field_checked = find_field(label)['checked'] + if field_checked.respond_to? :should + field_checked.should be_false + else + assert !field_checked + end + end +end + +Then /^(?:|I )should be on (.+)$/ do |page_name| + current_path = URI.parse(current_url).path + if current_path.respond_to? :should + current_path.should == path_to(page_name) + else + assert_equal path_to(page_name), current_path + end +end + +Then /^(?:|I )should have the following query string:$/ do |expected_pairs| + query = URI.parse(current_url).query + actual_params = query ? CGI.parse(query) : {} + expected_params = {} + expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')} + + if actual_params.respond_to? :should + actual_params.should == expected_params + else + assert_equal expected_params, actual_params + end +end + +Then /^show me the page$/ do + save_and_open_page +end diff --git a/recipebook/features/support/cleanup.rb b/recipebook/features/support/cleanup.rb new file mode 100644 index 00000000000..04e8b2f2949 --- /dev/null +++ b/recipebook/features/support/cleanup.rb @@ -0,0 +1,17 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +After do |scenario| + Recipe.all.each { |r| r.sdb_item.delete } + sleep 1 +end diff --git a/recipebook/features/support/env.rb b/recipebook/features/support/env.rb new file mode 100644 index 00000000000..2efd5b02d86 --- /dev/null +++ b/recipebook/features/support/env.rb @@ -0,0 +1,70 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + +ENV["RAILS_ENV"] ||= "test" +require File.expand_path(File.dirname(__FILE__) + '/../../config/environment') + +require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support +require 'cucumber/rails/world' +require 'cucumber/rails/active_record' +require 'cucumber/web/tableish' + +require 'capybara/rails' +require 'capybara/cucumber' +require 'capybara/session' +require 'cucumber/rails/capybara_javascript_emulation' # Lets you click links with onclick javascript handlers without using @culerity or @javascript +# Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In +# order to ease the transition to Capybara we set the default here. If you'd +# prefer to use XPath just remove this line and adjust any selectors in your +# steps to use the XPath syntax. +Capybara.default_selector = :css + +# If you set this to false, any error raised from within your app will bubble +# up to your step definition and out to cucumber unless you catch it somewhere +# on the way. You can make Rails rescue errors and render error pages on a +# per-scenario basis by tagging a scenario or feature with the @allow-rescue tag. +# +# If you set this to true, Rails will rescue all errors and render error +# pages, more or less in the same way your application would behave in the +# default production environment. It's not recommended to do this for all +# of your scenarios, as this makes it hard to discover errors in your application. +ActionController::Base.allow_rescue = false + +# If you set this to true, each scenario will run in a database transaction. +# You can still turn off transactions on a per-scenario basis, simply tagging +# a feature or scenario with the @no-txn tag. If you are using Capybara, +# tagging with @culerity or @javascript will also turn transactions off. +# +# If you set this to false, transactions will be off for all scenarios, +# regardless of whether you use @no-txn or not. +# +# Beware that turning transactions off will leave data in your database +# after each scenario, which can lead to hard-to-debug failures in +# subsequent scenarios. If you do this, we recommend you create a Before +# block that will explicitly put your database in a known state. +Cucumber::Rails::World.use_transactional_fixtures = true +# How to clean your database when transactions are turned off. See +# http://github.com/bmabey/database_cleaner for more info. +if defined?(ActiveRecord::Base) + begin + require 'database_cleaner' + DatabaseCleaner.strategy = :truncation + rescue LoadError => ignore_if_database_cleaner_not_present + end +end diff --git a/recipebook/features/support/paths.rb b/recipebook/features/support/paths.rb new file mode 100644 index 00000000000..bdccec68689 --- /dev/null +++ b/recipebook/features/support/paths.rb @@ -0,0 +1,46 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +module NavigationHelpers + # Maps a name to a path. Used by the + # + # When /^I go to (.+)$/ do |page_name| + # + # step definition in web_steps.rb + # + def path_to(page_name) + case page_name + + when /the home\s?page/ + '/' + + # Add more mappings here. + # Here is an example that pulls values out of the Regexp: + # + # when /^(.*)'s profile page$/i + # user_profile_path(User.find_by_login($1)) + + else + begin + page_name =~ /the (.*) page/ + path_components = $1.split(/\s+/) + self.send(path_components.push('path').join('_').to_sym) + rescue Object => e + raise "Can't find mapping from \"#{page_name}\" to a path.\n" + + "Now, go and add a mapping in #{__FILE__}" + end + end + end +end + +World(NavigationHelpers) diff --git a/recipebook/lib/tasks/cucumber.rake b/recipebook/lib/tasks/cucumber.rake new file mode 100644 index 00000000000..6498d4fb599 --- /dev/null +++ b/recipebook/lib/tasks/cucumber.rake @@ -0,0 +1,66 @@ +# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks + +vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? + +begin + require 'cucumber/rake/task' + + namespace :cucumber do + Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. + t.fork = true # You may get faster startup if you set this to false + t.profile = 'default' + end + + Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'wip' + end + + Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'rerun' + end + + desc 'Run all features' + task :all => [:ok, :wip] + end + desc 'Alias for cucumber:ok' + task :cucumber => 'cucumber:ok' + + task :default => :cucumber + + task :features => :cucumber do + STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" + end +rescue LoadError + desc 'cucumber rake task not available (cucumber not installed)' + task :cucumber do + abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' + end +end + +end diff --git a/recipebook/public/404.html b/recipebook/public/404.html new file mode 100644 index 00000000000..9a48320a5f1 --- /dev/null +++ b/recipebook/public/404.html @@ -0,0 +1,26 @@ + + + + The page you were looking for doesn't exist (404) + + + + + +
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+ + diff --git a/recipebook/public/422.html b/recipebook/public/422.html new file mode 100644 index 00000000000..83660ab1878 --- /dev/null +++ b/recipebook/public/422.html @@ -0,0 +1,26 @@ + + + + The change you wanted was rejected (422) + + + + + +
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+ + diff --git a/recipebook/public/500.html b/recipebook/public/500.html new file mode 100644 index 00000000000..b80307fc166 --- /dev/null +++ b/recipebook/public/500.html @@ -0,0 +1,26 @@ + + + + We're sorry, but something went wrong (500) + + + + + +
+

We're sorry, but something went wrong.

+

We've been notified about this issue and we'll take a look at it shortly.

+
+ + diff --git a/recipebook/public/favicon.ico b/recipebook/public/favicon.ico new file mode 100644 index 00000000000..e69de29bb2d diff --git a/recipebook/public/images/rails.png b/recipebook/public/images/rails.png new file mode 100644 index 0000000000000000000000000000000000000000..d5edc04e65f555e3ba4dcdaad39dc352e75b575e GIT binary patch literal 6646 zcmVpVcQya!6@Dsmj@#jv7C*qh zIhOJ6_K0n?*d`*T7TDuW-}m`9Kz3~>+7`DUkbAraU%yi+R{N~~XA2B%zt-4=tLimUer9!2M~N{G5bftFij_O&)a zsHnOppFIzebQ`RA0$!yUM-lg#*o@_O2wf422iLnM6cU(ktYU8#;*G!QGhIy9+ZfzKjLuZo%@a z-i@9A`X%J{^;2q&ZHY3C(B%gqCPW!8{9C0PMcNZccefK){s|V5-xxtHQc@uf>XqhD z7#N^siWqetgq29aX>G^olMf=bbRF6@Y(}zYxw6o!9WBdG1unP}<(V;zKlcR2p86fq zYjaqB^;Ycq>Wy@5T1xOzG3tucG3e%nPvajaN{CrFbnzv^9&K3$NrDm*eQe4`BGQ2bI;dFEwyt>hK%X!L6)82aOZp zsrGcJ#7PoX7)s|~t6is?FfX*7vWdREi58tiY4S)t6u*|kv?J)d_$r+CH#eZ?Ef+I_ z(eVlX8dh~4QP?o*E`_MgaNFIKj*rtN(0Raj3ECjSXcWfd#27NYs&~?t`QZFT}!Zaf=ldZIhi}LhQlqLo+o5(Pvui&{7PD__^53f9j>HW`Q z_V8X5j~$|GP9qXu0C#!@RX2}lXD35@3N5{BkUi%jtaPQ*H6OX2zIz4QPuqmTv3`vG{zc>l3t0B9E75h< z8&twGh%dp7WPNI+tRl%#gf2}Epg8st+~O4GjtwJsXfN;EjAmyr6z5dnaFU(;IV~QK zW62fogF~zA``(Q>_SmD!izc6Y4zq*97|NAPHp1j5X7Op2%;GLYm>^HEMyObo6s7l) zE3n|aOHi5~B84!}b^b*-aL2E)>OEJX_tJ~t<#VJ?bT?lDwyDB&5SZ$_1aUhmAY}#* zs@V1I+c5md9%R-o#_DUfqVtRk>59{+Opd5Yu%dAU#VQW}^m}x-30ftBx#527{^pI4 z6l2C6C7QBG$~NLYb3rVdLD#Z{+SleOp`(Lg5J}`kxdTHe(nV5BdpLrD=l|)e$gEqA zwI6vuX-PFCtcDIH>bGY2dwq&^tf+&R?)nY-@7_j%4CMRAF}C9w%p86W<2!aSY$p+k zrkFtG=cGo38RnrG28;?PNk%7a@faaXq&MS*&?1Z`7Ojw7(#>}ZG4nMAs3VXxfdW>i zY4VX02c5;f7jDPY_7@Oa)CHH}cH<3y#}_!nng^W+h1e-RL*YFYOteC@h?BtJZ+?sE zy)P5^8Mregx{nQaw1NY-|3>{Z)|0`?zc?G2-acYiSU`tj#sSGfm7k86ZQ0SQgPevcklHxM9<~4yW zR796sisf1|!#{Z=e^)0;_8iUhL8g(;j$l=02FTPZ(dZV@s#aQ`DHkLM6=YsbE4iQ!b#*374l0Jw5;jD%J;vQayq=nD8-kHI~f9Ux|32SJUM`> zGp2UGK*4t?cRKi!2he`zI#j0f${I#f-jeT?u_C7S4WsA0)ryi-1L0(@%pa^&g5x=e z=KW9+Nn(=)1T&S8g_ug%dgk*~l2O-$r9#zEGBdQsweO%t*6F4c8JC36JtTizCyy+E4h%G(+ z5>y$%0txMuQ$e~wjFgN(xrAndHQo`Za+K*?gUVDTBV&Ap^}|{w#CIq{DRe}+l@(Ec zCCV6f_?dY_{+f{}6XGn!pL_up?}@>KijT^$w#Lb6iHW&^8RP~g6y=vZBXx~B9nI^i zGexaPjcd(%)zGw!DG_dDwh-7x6+ST#R^${iz_M$uM!da8SxgB_;Z0G%Y*HpvLjKw; zX=ir7i1O$-T|*TBoH$dlW+TLf5j5sep^DlDtkox;Kg{Q%EXWedJq@J@%VAcK)j3y1 zShM!CS#qax;D@RND%2t3W6kv+#Ky0F9<3YKDbV^XJ=^$s(Vtza8V72YY)577nnldI zHMA0PUo!F3j(ubV*CM@PiK<^|RM2(DuCbG7`W}Rg(xdYC>C~ z;1KJGLN&$cRxSZunjXcntykmpFJ7;dk>shY(DdK&3K_JDJ6R%D`e~6Qv67@Rwu+q9 z*|NG{r}4F8f{Dfzt0+cZMd$fvlX3Q`dzM46@r?ISxr;9gBTG2rmfiGOD*#c*3f)cc zF+PFZobY$-^}J8 z%n=h4;x2}cP!@SiVd!v;^Wwo0(N??-ygDr7gG^NKxDjSo{5T{?$|Qo5;8V!~D6O;F*I zuY!gd@+2j_8Rn=UWDa#*4E2auWoGYDddMW7t0=yuC(xLWky?vLimM~!$3fgu!dR>p z?L?!8z>6v$|MsLb&dU?ob)Zd!B)!a*Z2eTE7 zKCzP&e}XO>CT%=o(v+WUY`Az*`9inbTG& z_9_*oQKw;sc8{ipoBC`S4Tb7a%tUE)1fE+~ib$;|(`|4QbXc2>VzFi%1nX%ti;^s3~NIL0R}!!a{0A zyCRp0F7Y&vcP&3`&Dzv5!&#h}F2R-h&QhIfq*ts&qO13{_CP}1*sLz!hI9VoTSzTu zok5pV0+~jrGymE~{TgbS#nN5+*rF7ij)cnSLQw0Ltc70zmk|O!O(kM<3zw-sUvkx~ z2`y+{xAwKSa-0}n7{$I@Zop7CWy%_xIeN1e-7&OjQ6vZZPbZ^3_ z(~=;ZSP98S2oB#35b1~_x`2gWiPdIVddEf`AD9<@c_s)TM;3J$T_l?pr{<7PTgdiy zBc5IGx)g~n=s+Z$RzYCmv8PlJu%gkh^;%mTGMc)UwRINVD~K;`Rl!5@hhGg;y>5qj zq|u-Yf0q_~Y+Mbivkkfa0nAOzB1acnytogsj_m7FB(-FjihMek#GAU4M!iXCgdK8a zjoKm?*|iz7;dHm4$^hh(`Ufl>yb>$hjIA-;>{>C}G0Di%bGvUsJkfLAV|xq32c>RqJqTBJ3Dx zYC;*Dt|S$b6)aCJFnK(Eey$M1DpVV~_MIhwK> zygo(jWC|_IRw|456`roEyXtkNLWNAt-4N1qyN$I@DvBzt;e|?g<*HK1%~cq|^u*}C zmMrwh>{QAq?Ar~4l^DqT%SQ)w)FA(#7#u+N;>E975rYML>)LgE`2<7nN=C1pC{IkV zVw}_&v6j&S?QVh*)wF3#XmE@0($^BVl1969csLKUBNer{suVd!a~B!0MxWY?=(GD6 zy$G&ERFR#i6G4=2F?R4}Mz3B?3tnpoX3)qFF2sh9-Jn*e%9F>i{WG7$_~XyOO2!+@ z6k+38KyD@-0=uee54D0!Z1@B^ilj~StchdOn(*qvg~s5QJpWGc!6U^Aj!xt-HZn_V zS%|fyQ5YS@EP2lBIodXCLjG_+a)%En+7jzngk@J>6D~^xbxKkvf-R0-c%mX+o{?&j zZZ%RxFeav8Y0gkwtdtrwUb-i0Egd2C=ADu%w5VV-hNJvl)GZ?M;y$!?b=S+wKRK7Q zcOjPT!p<*#8m;TsBih=@Xc&c)?Vy`Ys>IvK@|1%N+M6J-^RCRaZcPP2eQh9DEGZr+ z?8B~wF14mk4Xkuen{wY^CWwS1PI<8gikY*)3?RSo5l8es4*J z43k_BIwc}of=6Pfs%xIxlMDGOJN zvl!a>G)52XMqA%fbgkZi%)%bN*ZzZw2!rn4@+J)2eK#kWuEW{)W~-`y1vhA5-7p%R z&f5N!a9f8cK1Xa=O}=9{wg%}Ur^+8Y(!UCeqw>%wj@|bYHD-bZO~mk3L$9_^MmF3G zvCiK^e@q6G?tHkM8%GqsBMZaB20W$UEt_5r~jc#WlR>Bv{6W>A=!#InoY zLOd04@Rz?*7PpW8u|+}bt`?+Z(GsX{Br4A2$ZZ(26Degmr9`O=t2KgHTL*==R3xcP z&Y(J7hC@6_x8zVz!CX3l4Xtss6i7r#E6kXMNN1~>9KTRzewfp))ij%)SBBl0fZdYP zd!zzQD5u8yk-u|41|Rqz7_tCFUMThZJVj)yQf6^Cwtn|Ew6cm5J|u1Bq>MWX-AfB&NE;C z62@=-0le`E6-CurMKjoIy)BuUmhMGJb}pPx!@GLWMT+wH2R?wA=MEy)o57~feFp8P zY@YXAyt4<1FD<|iw{FGQu~GEI<4C64)V*QiVk+VzOV^9GWf4ir#oYgHJz!wq>iZV#_6@_{)&lum)4x z_Of*CLVQ7wdT#XT-(h0qH%mcIF7yzMIvvTN3bPceK>PpJi(=3Nny zbSn}p$dGKQUlX&-t~RR)#F7I<8NCD^yke(vdf#4^aAh}M-{tS9-&^tC4`KU_pToXy z+|K8sx}a)Kh{h{;*V1#hs1xB%(?j>)g~`Wv(9F)f=Qn)(daVB7hZtcp^#LrEr1T1J zZSJ*lVyVVjhy)mkex9Whn=EinKDHe@KlfQI-Fl7M?-c~HnW0;C;+MbUY8?FToy;A+ zs&Nc7VZ=Of+e!G6s#+S5WBU)kgQq_I1@!uH74GJ-+O|%0HXm9Mqlvp|j%0`T>fr9^ zK;qo>XdwZW<>%tTA+<(1^6(>=-2N;hRgBnjvEjN;VbKMbFg--WrGy|XESoH1p|M4` z86(gC^vB4qScASZ&cdpT{~QDN-jC|GJ(RYoW1VW4!SSn- zhQds9&RBKn6M&GVK_Aayt(Hekbnw=tr>f z^o@v9_*iQO1*zeOrts9Q-$pc@!StS&kz$cF`s@pM`rmJXTP&h5G)A74!0e%ZJbl}( zssI|_!%~_hZFypv*S^JE5N&Kvmx7KiG<|fGMO=WrH+@Yhuj+KwiS#l4>@%2nl zS)mDikfmokO4q2A)hRVZBq2-5q&XC>%HOLkOYxZ66(s86?=0s4z5xbiOV)}L-&6b)h6(~CIaR#JNw~46+WBiU7IhB zq!NuR4!TsYnyBg>@G=Ib*cMq^k<}AMpCeYEf&dzfiGI-wOQ7hb+nA zkN7_){y&c3xC0 AQ~&?~ literal 0 HcmV?d00001 diff --git a/recipebook/public/javascripts/application.js b/recipebook/public/javascripts/application.js new file mode 100644 index 00000000000..351274cea0c --- /dev/null +++ b/recipebook/public/javascripts/application.js @@ -0,0 +1,45 @@ +/* + * Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You + * may not use this file except in compliance with the License. A copy of + * the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ + +$(".remove_ingredient").live( + "click", + function(e) { + e.preventDefault(); + var target = $(e.target); + target.parent().slideUp( + 'fast', + function() { + target.parent().remove(); + } + ); + } +); + +$(".add_ingredient").live( + "click", + function(e) { + e.preventDefault(); + var ingredient = $('
  • ').append( + $(''), + "\u00a0", + $('') + .addClass("remove_ingredient") + .text("Remove") + ); + ingredient.hide(); + $('#ingredient_list').append(ingredient); + ingredient.slideDown(); + } +); diff --git a/recipebook/public/javascripts/controls.js b/recipebook/public/javascripts/controls.js new file mode 100644 index 00000000000..7392fb664c0 --- /dev/null +++ b/recipebook/public/javascripts/controls.js @@ -0,0 +1,965 @@ +// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2009 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = { }; +Autocompleter.Base = Class.create({ + baseInitialize: function(element, update, options) { + element = $(element); + this.element = element; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + this.oldElementValue = this.element.value; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || { }; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + // Force carriage returns as token delimiters anyway + if (!this.options.tokens.include('\n')) + this.options.tokens.push('\n'); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (Prototype.Browser.IE) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index--; + else this.index = this.entryCount-1; + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++; + else this.index = 0; + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = $(selectedElement).select('.' + this.options.select) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var bounds = this.getTokenBounds(); + if (bounds[0] != -1) { + var newValue = this.element.value.substr(0, bounds[0]); + var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value + this.element.value.substr(bounds[1]); + } else { + this.element.value = value; + } + this.oldElementValue = this.element.value; + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + this.entryCount = + this.update.down().childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + this.tokenBounds = null; + if(this.getToken().length>=this.options.minChars) { + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + this.oldElementValue = this.element.value; + }, + + getToken: function() { + var bounds = this.getTokenBounds(); + return this.element.value.substring(bounds[0], bounds[1]).strip(); + }, + + getTokenBounds: function() { + if (null != this.tokenBounds) return this.tokenBounds; + var value = this.element.value; + if (value.strip().empty()) return [-1, 0]; + var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); + var offset = (diff == this.oldElementValue.length ? 1 : 0); + var prevTokenPos = -1, nextTokenPos = value.length; + var tp; + for (var index = 0, l = this.options.tokens.length; index < l; ++index) { + tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); + if (tp > prevTokenPos) prevTokenPos = tp; + tp = value.indexOf(this.options.tokens[index], diff + offset); + if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; + } + return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); + } +}); + +Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { + var boundary = Math.min(newS.length, oldS.length); + for (var index = 0; index < boundary; ++index) + if (newS[index] != oldS[index]) + return index; + return boundary; +}; + +Ajax.Autocompleter = Class.create(Autocompleter.Base, { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + this.startIndicator(); + + var entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(Autocompleter.Base, { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); + return "
      " + ret.join('') + "
    "; + } + }, options || { }); + } +}); + +// AJAX in-place editor and collection editor +// Full rewrite by Christophe Porteneuve (April 2007). + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +}; + +Ajax.InPlaceEditor = Class.create({ + initialize: function(element, url, options) { + this.url = url; + this.element = element = $(element); + this.prepareOptions(); + this._controls = { }; + arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! + Object.extend(this.options, options || { }); + if (!this.options.formId && this.element.id) { + this.options.formId = this.element.id + '-inplaceeditor'; + if ($(this.options.formId)) + this.options.formId = ''; + } + if (this.options.externalControl) + this.options.externalControl = $(this.options.externalControl); + if (!this.options.externalControl) + this.options.externalControlOnly = false; + this._originalBackground = this.element.getStyle('background-color') || 'transparent'; + this.element.title = this.options.clickToEditText; + this._boundCancelHandler = this.handleFormCancellation.bind(this); + this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); + this._boundFailureHandler = this.handleAJAXFailure.bind(this); + this._boundSubmitHandler = this.handleFormSubmission.bind(this); + this._boundWrapperHandler = this.wrapUp.bind(this); + this.registerListeners(); + }, + checkForEscapeOrReturn: function(e) { + if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; + if (Event.KEY_ESC == e.keyCode) + this.handleFormCancellation(e); + else if (Event.KEY_RETURN == e.keyCode) + this.handleFormSubmission(e); + }, + createControl: function(mode, handler, extraClasses) { + var control = this.options[mode + 'Control']; + var text = this.options[mode + 'Text']; + if ('button' == control) { + var btn = document.createElement('input'); + btn.type = 'submit'; + btn.value = text; + btn.className = 'editor_' + mode + '_button'; + if ('cancel' == mode) + btn.onclick = this._boundCancelHandler; + this._form.appendChild(btn); + this._controls[mode] = btn; + } else if ('link' == control) { + var link = document.createElement('a'); + link.href = '#'; + link.appendChild(document.createTextNode(text)); + link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; + link.className = 'editor_' + mode + '_link'; + if (extraClasses) + link.className += ' ' + extraClasses; + this._form.appendChild(link); + this._controls[mode] = link; + } + }, + createEditField: function() { + var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); + var fld; + if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { + fld = document.createElement('input'); + fld.type = 'text'; + var size = this.options.size || this.options.cols || 0; + if (0 < size) fld.size = size; + } else { + fld = document.createElement('textarea'); + fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); + fld.cols = this.options.cols || 40; + } + fld.name = this.options.paramName; + fld.value = text; // No HTML breaks conversion anymore + fld.className = 'editor_field'; + if (this.options.submitOnBlur) + fld.onblur = this._boundSubmitHandler; + this._controls.editor = fld; + if (this.options.loadTextURL) + this.loadExternalText(); + this._form.appendChild(this._controls.editor); + }, + createForm: function() { + var ipe = this; + function addText(mode, condition) { + var text = ipe.options['text' + mode + 'Controls']; + if (!text || condition === false) return; + ipe._form.appendChild(document.createTextNode(text)); + }; + this._form = $(document.createElement('form')); + this._form.id = this.options.formId; + this._form.addClassName(this.options.formClassName); + this._form.onsubmit = this._boundSubmitHandler; + this.createEditField(); + if ('textarea' == this._controls.editor.tagName.toLowerCase()) + this._form.appendChild(document.createElement('br')); + if (this.options.onFormCustomization) + this.options.onFormCustomization(this, this._form); + addText('Before', this.options.okControl || this.options.cancelControl); + this.createControl('ok', this._boundSubmitHandler); + addText('Between', this.options.okControl && this.options.cancelControl); + this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); + addText('After', this.options.okControl || this.options.cancelControl); + }, + destroy: function() { + if (this._oldInnerHTML) + this.element.innerHTML = this._oldInnerHTML; + this.leaveEditMode(); + this.unregisterListeners(); + }, + enterEditMode: function(e) { + if (this._saving || this._editing) return; + this._editing = true; + this.triggerCallback('onEnterEditMode'); + if (this.options.externalControl) + this.options.externalControl.hide(); + this.element.hide(); + this.createForm(); + this.element.parentNode.insertBefore(this._form, this.element); + if (!this.options.loadTextURL) + this.postProcessEditField(); + if (e) Event.stop(e); + }, + enterHover: function(e) { + if (this.options.hoverClassName) + this.element.addClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onEnterHover'); + }, + getText: function() { + return this.element.innerHTML.unescapeHTML(); + }, + handleAJAXFailure: function(transport) { + this.triggerCallback('onFailure', transport); + if (this._oldInnerHTML) { + this.element.innerHTML = this._oldInnerHTML; + this._oldInnerHTML = null; + } + }, + handleFormCancellation: function(e) { + this.wrapUp(); + if (e) Event.stop(e); + }, + handleFormSubmission: function(e) { + var form = this._form; + var value = $F(this._controls.editor); + this.prepareSubmission(); + var params = this.options.callback(form, value) || ''; + if (Object.isString(params)) + params = params.toQueryParams(); + params.editorId = this.element.id; + if (this.options.htmlResponse) { + var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Updater({ success: this.element }, this.url, options); + } else { + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.url, options); + } + if (e) Event.stop(e); + }, + leaveEditMode: function() { + this.element.removeClassName(this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + if (this.options.externalControl) + this.options.externalControl.show(); + this._saving = false; + this._editing = false; + this._oldInnerHTML = null; + this.triggerCallback('onLeaveEditMode'); + }, + leaveHover: function(e) { + if (this.options.hoverClassName) + this.element.removeClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onLeaveHover'); + }, + loadExternalText: function() { + this._form.addClassName(this.options.loadingClassName); + this._controls.editor.disabled = true; + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._form.removeClassName(this.options.loadingClassName); + var text = transport.responseText; + if (this.options.stripLoadedTextTags) + text = text.stripTags(); + this._controls.editor.value = text; + this._controls.editor.disabled = false; + this.postProcessEditField(); + }.bind(this), + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + postProcessEditField: function() { + var fpc = this.options.fieldPostCreation; + if (fpc) + $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); + }, + prepareOptions: function() { + this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); + Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); + [this._extraDefaultOptions].flatten().compact().each(function(defs) { + Object.extend(this.options, defs); + }.bind(this)); + }, + prepareSubmission: function() { + this._saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + registerListeners: function() { + this._listeners = { }; + var listener; + $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { + listener = this[pair.value].bind(this); + this._listeners[pair.key] = listener; + if (!this.options.externalControlOnly) + this.element.observe(pair.key, listener); + if (this.options.externalControl) + this.options.externalControl.observe(pair.key, listener); + }.bind(this)); + }, + removeForm: function() { + if (!this._form) return; + this._form.remove(); + this._form = null; + this._controls = { }; + }, + showSaving: function() { + this._oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + this.element.addClassName(this.options.savingClassName); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + }, + triggerCallback: function(cbName, arg) { + if ('function' == typeof this.options[cbName]) { + this.options[cbName](this, arg); + } + }, + unregisterListeners: function() { + $H(this._listeners).each(function(pair) { + if (!this.options.externalControlOnly) + this.element.stopObserving(pair.key, pair.value); + if (this.options.externalControl) + this.options.externalControl.stopObserving(pair.key, pair.value); + }.bind(this)); + }, + wrapUp: function(transport) { + this.leaveEditMode(); + // Can't use triggerCallback due to backward compatibility: requires + // binding + direct element + this._boundComplete(transport, this.element); + } +}); + +Object.extend(Ajax.InPlaceEditor.prototype, { + dispose: Ajax.InPlaceEditor.prototype.destroy +}); + +Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { + initialize: function($super, element, url, options) { + this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; + $super(element, url, options); + }, + + createEditField: function() { + var list = document.createElement('select'); + list.name = this.options.paramName; + list.size = 1; + this._controls.editor = list; + this._collection = this.options.collection || []; + if (this.options.loadCollectionURL) + this.loadCollection(); + else + this.checkForExternalText(); + this._form.appendChild(this._controls.editor); + }, + + loadCollection: function() { + this._form.addClassName(this.options.loadingClassName); + this.showLoadingText(this.options.loadingCollectionText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + var js = transport.responseText.strip(); + if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check + throw('Server returned an invalid collection representation.'); + this._collection = eval(js); + this.checkForExternalText(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadCollectionURL, options); + }, + + showLoadingText: function(text) { + this._controls.editor.disabled = true; + var tempOption = this._controls.editor.firstChild; + if (!tempOption) { + tempOption = document.createElement('option'); + tempOption.value = ''; + this._controls.editor.appendChild(tempOption); + tempOption.selected = true; + } + tempOption.update((text || '').stripScripts().stripTags()); + }, + + checkForExternalText: function() { + this._text = this.getText(); + if (this.options.loadTextURL) + this.loadExternalText(); + else + this.buildOptionList(); + }, + + loadExternalText: function() { + this.showLoadingText(this.options.loadingText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._text = transport.responseText.strip(); + this.buildOptionList(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + + buildOptionList: function() { + this._form.removeClassName(this.options.loadingClassName); + this._collection = this._collection.map(function(entry) { + return 2 === entry.length ? entry : [entry, entry].flatten(); + }); + var marker = ('value' in this.options) ? this.options.value : this._text; + var textFound = this._collection.any(function(entry) { + return entry[0] == marker; + }.bind(this)); + this._controls.editor.update(''); + var option; + this._collection.each(function(entry, index) { + option = document.createElement('option'); + option.value = entry[0]; + option.selected = textFound ? entry[0] == marker : 0 == index; + option.appendChild(document.createTextNode(entry[1])); + this._controls.editor.appendChild(option); + }.bind(this)); + this._controls.editor.disabled = false; + Field.scrollFreeActivate(this._controls.editor); + } +}); + +//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** +//**** This only exists for a while, in order to let **** +//**** users adapt to the new API. Read up on the new **** +//**** API and convert your code to it ASAP! **** + +Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { + if (!options) return; + function fallback(name, expr) { + if (name in options || expr === undefined) return; + options[name] = expr; + }; + fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : + options.cancelLink == options.cancelButton == false ? false : undefined))); + fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : + options.okLink == options.okButton == false ? false : undefined))); + fallback('highlightColor', options.highlightcolor); + fallback('highlightEndColor', options.highlightendcolor); +}; + +Object.extend(Ajax.InPlaceEditor, { + DefaultOptions: { + ajaxOptions: { }, + autoRows: 3, // Use when multi-line w/ rows == 1 + cancelControl: 'link', // 'link'|'button'|false + cancelText: 'cancel', + clickToEditText: 'Click to edit', + externalControl: null, // id|elt + externalControlOnly: false, + fieldPostCreation: 'activate', // 'activate'|'focus'|false + formClassName: 'inplaceeditor-form', + formId: null, // id|elt + highlightColor: '#ffff99', + highlightEndColor: '#ffffff', + hoverClassName: '', + htmlResponse: true, + loadingClassName: 'inplaceeditor-loading', + loadingText: 'Loading...', + okControl: 'button', // 'link'|'button'|false + okText: 'ok', + paramName: 'value', + rows: 1, // If 1 and multi-line, uses autoRows + savingClassName: 'inplaceeditor-saving', + savingText: 'Saving...', + size: 0, + stripLoadedTextTags: false, + submitOnBlur: false, + textAfterControls: '', + textBeforeControls: '', + textBetweenControls: '' + }, + DefaultCallbacks: { + callback: function(form) { + return Form.serialize(form); + }, + onComplete: function(transport, element) { + // For backward compatibility, this one is bound to the IPE, and passes + // the element directly. It was too often customized, so we don't break it. + new Effect.Highlight(element, { + startcolor: this.options.highlightColor, keepBackgroundImage: true }); + }, + onEnterEditMode: null, + onEnterHover: function(ipe) { + ipe.element.style.backgroundColor = ipe.options.highlightColor; + if (ipe._effect) + ipe._effect.cancel(); + }, + onFailure: function(transport, ipe) { + alert('Error communication with the server: ' + transport.responseText.stripTags()); + }, + onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. + onLeaveEditMode: null, + onLeaveHover: function(ipe) { + ipe._effect = new Effect.Highlight(ipe.element, { + startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, + restorecolor: ipe._originalBackground, keepBackgroundImage: true + }); + } + }, + Listeners: { + click: 'enterEditMode', + keydown: 'checkForEscapeOrReturn', + mouseover: 'enterHover', + mouseout: 'leaveHover' + } +}); + +Ajax.InPlaceCollectionEditor.DefaultOptions = { + loadingCollectionText: 'Loading options...' +}; + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create({ + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}); \ No newline at end of file diff --git a/recipebook/public/javascripts/dragdrop.js b/recipebook/public/javascripts/dragdrop.js new file mode 100644 index 00000000000..15c6dbca68b --- /dev/null +++ b/recipebook/public/javascripts/dragdrop.js @@ -0,0 +1,974 @@ +// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(Object.isUndefined(Effect)) + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || { }); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if(Object.isArray(containment)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var drop, affected = []; + + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) + drop = Droppables.findDeepestChild(affected); + + if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); + if (drop) { + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + if (drop != this.last_active) Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) { + this.last_active.onDrop(element, this.last_active.element, event); + return true; + } + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +}; + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); + } else { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + } + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +}; + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create({ + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) + Object.extend(defaults, { + starteffect: function(element) { + element._opacity = Element.getOpacity(element); + Draggable._dragging[element] = true; + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + } + }); + + var options = Object.extend(defaults, arguments[1] || { }); + + this.element = $(element); + + if(options.handle && Object.isString(options.handle)) + this.handle = this.element.down('.'+options.handle, 0); + + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = $(options.scroll); + this._isScrollChild = Element.childOf(this.element, options.scroll); + } + + Element.makePositioned(this.element); // fix IE + + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(!Object.isUndefined(Draggable._dragging[this.element]) && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if((tag_name = src.tagName.toUpperCase()) && ( + tag_name=='INPUT' || + tag_name=='SELECT' || + tag_name=='OPTION' || + tag_name=='BUTTON' || + tag_name=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = this.element.cumulativeOffset(); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + if(!this.delta) + this.delta = this.currentDelta(); + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); + if (!this._originallyAbsolute) + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + + if(!this.options.quiet){ + Position.prepare(); + Droppables.show(pointer, this.element); + } + + Draggables.notify('onDrag', this, event); + + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.quiet){ + Position.prepare(); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + Droppables.show(pointer, this.element); + } + + if(this.options.ghosting) { + if (!this._originallyAbsolute) + Position.relativize(this.element); + delete this._originallyAbsolute; + Element.remove(this._clone); + this._clone = null; + } + + var dropped = false; + if(success) { + dropped = Droppables.fire(event, this.element); + if (!dropped) dropped = false; + } + if(dropped && this.options.onDropped) this.options.onDropped(this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && Object.isFunction(revert)) revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + if (dropped == 0 || revert != 'failure') + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = this.element.cumulativeOffset(); + if(this.options.ghosting) { + var r = Position.realOffset(this.element); + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; + } + + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(Object.isFunction(this.options.snap)) { + p = this.options.snap(p[0],p[1],this); + } else { + if(Object.isArray(this.options.snap)) { + p = p.map( function(v, i) { + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); + } else { + p = p.map( function(v) { + return (v/this.options.snap).round()*this.options.snap }.bind(this)); + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + if(!(speed[0] || speed[1])) return; + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + } + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight; + } + } + return { top: T, left: L, width: W, height: H }; + } +}); + +Draggable._dragging = { }; + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create({ + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +}); + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: { }, + + _findRootElement: function(element) { + while (element.tagName.toUpperCase() != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + element = $(element); + var s = Sortable.sortables[element.id]; + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + + // these take arrays of elements or ids and can be + // used for better initialization performance + elements: false, + handles: false, + + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || { }); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + quiet: options.quiet, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + }; + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + }; + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (options.elements || this.findElements(element, options) || []).each( function(e,i) { + var handle = options.handles ? $(options.handles[i]) : + (options.handle ? $(e).select('.' + options.handle)[0] : e); + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.identify()] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = dropon.cumulativeOffset(); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); + else + Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); + + Sortable._marker.show(); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: [], + position: parent.children.length, + container: $(children[i]).down(options.treeTag) + }; + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child); + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || { }); + + var root = { + id: null, + parent: null, + children: [], + container: element, + position: 0 + }; + + return Sortable._tree(element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || { }); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || { }); + + var nodeMap = { }; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || { }); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +}; + +// Returns true if child is contained within element +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + if (child.parentNode == element) return true; + return Element.isParent(child.parentNode, element); +}; + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +}; + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +}; \ No newline at end of file diff --git a/recipebook/public/javascripts/effects.js b/recipebook/public/javascripts/effects.js new file mode 100644 index 00000000000..c81e6c7d5f4 --- /dev/null +++ b/recipebook/public/javascripts/effects.js @@ -0,0 +1,1123 @@ +// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if (this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if (this.slice(0,1) == '#') { + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if (this.length==7) color = this.toLowerCase(); + } + } + return (color.length==7 ? color : (arguments[0] || this)); +}; + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +}; + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +}; + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if (Prototype.Browser.WebKit) window.scrollBy(0,0); + return element; +}; + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +}; + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + Transitions: { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + .5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; + return pos > 1 ? 1 : pos; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; + }, + pulse: function(pos, pulses) { + return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; + }, + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } + }, + DefaultOptions: { + duration: 1.0, // seconds + fps: 100, // 100= assume 66fps max. + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if (child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + new Element('span', {style: tagifyStyle}).update( + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if (((typeof element == 'object') || + Object.isFunction(element)) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || { }); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect, options) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + + return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, options || {})); + } +}; + +Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(Enumerable, { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = Object.isString(effect.options.queue) ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if (!this.interval) + this.interval = setInterval(this.loop.bind(this), 15); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if (this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i=0, len=this.effects.length;i= this.startOn) { + if (timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if (this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / this.totalTime, + frame = (pos * this.totalFrames).round(); + if (frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + cancel: function() { + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if (this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + var data = $H(); + for(property in this) + if (!Object.isFunction(this[property])) data.set(property, this[property]); + return '#'; + } +}); + +Effect.Parallel = Class.create(Effect.Base, { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if (effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Tween = Class.create(Effect.Base, { + initialize: function(object, from, to) { + object = Object.isString(object) ? $(object) : object; + var args = $A(arguments), method = args.last(), + options = args.length == 5 ? args[3] : null; + this.method = Object.isFunction(method) ? method.bind(object) : + Object.isFunction(object[method]) ? object[method].bind(object) : + function(value) { object[method] = value }; + this.start(Object.extend({ from: from, to: to }, options || { })); + }, + update: function(position) { + this.method(position); + } +}); + +Effect.Event = Class.create(Effect.Base, { + initialize: function() { + this.start(Object.extend({ duration: 0 }, arguments[0] || { })); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || { }); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if (this.options.mode == 'absolute') { + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: (this.options.x * position + this.originalLeft).round() + 'px', + top: (this.options.y * position + this.originalTop).round() + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); +}; + +Effect.Scale = Class.create(Effect.Base, { + initialize: function(element, percent) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or { } with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || { }); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = { }; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if (fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if (this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if (/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if (!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if (this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = { }; + if (this.options.scaleX) d.width = width.round() + 'px'; + if (this.options.scaleY) d.height = height.round() + 'px'; + if (this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if (this.elementPositioning == 'absolute') { + if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if (this.options.scaleY) d.top = -topd + 'px'; + if (this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if (this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { }; + if (!this.options.keepBackgroundImage) { + this.oldStyle.backgroundImage = this.element.getStyle('background-image'); + this.element.setStyle({backgroundImage: 'none'}); + } + if (!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if (!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = function(element) { + var options = arguments[1] || { }, + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(); + + if (options.offset) elementOffsets[1] += options.offset; + + return new Effect.Tween(null, + scrollOffsets.top, + elementOffsets[1], + options, + function(p){ scrollTo(scrollOffsets.left, p.round()); } + ); +}; + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if (effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + } + }, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from).show(); + }}, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || { }) + ); +}; + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || { }) + ); +}; + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || { })); +}; + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }); + } + }, arguments[1] || { })); +}; + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || { })); +}; + +Effect.Shake = function(element) { + element = $(element); + var options = Object.extend({ + distance: 20, + duration: 0.5 + }, arguments[1] || {}); + var distance = parseFloat(options.distance); + var split = parseFloat(options.duration) / 10.0; + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}); }}); }}); }}); }}); }}); +}; + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || { }) + ); +}; + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); + } + }, arguments[1] || { }) + ); +}; + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +}; + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ); + } + }); +}; + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +}; + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || { }, + oldOpacity = element.getInlineOpacity(), + transition = options.transition || Effect.Transitions.linear, + reverser = function(pos){ + return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); + }; + + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +}; + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || { })); +}; + +Effect.Morph = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: { } + }, arguments[1] || { }); + + if (!Object.isString(options.style)) this.style = $H(options.style); + else { + if (options.style.include(':')) + this.style = options.style.parseStyle(); + else { + this.element.addClassName(options.style); + this.style = $H(this.element.getStyles()); + this.element.removeClassName(options.style); + var css = this.element.getStyles(); + this.style = this.style.reject(function(style) { + return style.value == css[style.key]; + }); + options.afterFinishInternal = function(effect) { + effect.element.addClassName(effect.options.style); + effect.transforms.each(function(transform) { + effect.element.style[transform.style] = ''; + }); + }; + } + } + this.start(options); + }, + + setup: function(){ + function parseColor(color){ + if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; + color = color.parseColor(); + return $R(0,2).map(function(i){ + return parseInt( color.slice(i*2+1,i*2+3), 16 ); + }); + } + this.transforms = this.style.map(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if (value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if (property == 'opacity') { + value = parseFloat(value); + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + } else if (Element.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + } + + var originalValue = this.element.getStyle(property); + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: unit=='color' ? parseColor(value) : value, + unit: unit + }; + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ); + }); + }, + update: function(position) { + var style = { }, transform, i = this.transforms.length; + while(i--) + style[(transform = this.transforms[i]).style] = + transform.unit=='color' ? '#'+ + (Math.round(transform.originalValue[0]+ + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + + (Math.round(transform.originalValue[1]+ + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + + (Math.round(transform.originalValue[2]+ + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : + (transform.originalValue + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.unit === null ? '' : transform.unit); + this.element.setStyle(style, true); + } +}); + +Effect.Transform = Class.create({ + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || { }; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + track = $H(track); + var data = track.values().first(); + this.tracks.push($H({ + ids: track.keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); + var elements = [$(ids) || $$(ids)].flatten(); + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = $w( + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + + 'fontSize fontWeight height left letterSpacing lineHeight ' + + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + + 'right textIndent top width wordSpacing zIndex'); + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.__parseStyleElement = document.createElement('div'); +String.prototype.parseStyle = function(){ + var style, styleRules = $H(); + if (Prototype.Browser.WebKit) + style = new Element('div',{style:this}).style; + else { + String.__parseStyleElement.innerHTML = '
    '; + style = String.__parseStyleElement.childNodes[0].style; + } + + Element.CSS_PROPERTIES.each(function(property){ + if (style[property]) styleRules.set(property, style[property]); + }); + + if (Prototype.Browser.IE && this.include('opacity')) + styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); + + return styleRules; +}; + +if (document.defaultView && document.defaultView.getComputedStyle) { + Element.getStyles = function(element) { + var css = document.defaultView.getComputedStyle($(element), null); + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { + styles[property] = css[property]; + return styles; + }); + }; +} else { + Element.getStyles = function(element) { + element = $(element); + var css = element.currentStyle, styles; + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { + results[property] = css[property]; + return results; + }); + if (!styles.opacity) styles.opacity = element.getOpacity(); + return styles; + }; +} + +Effect.Methods = { + morph: function(element, style) { + element = $(element); + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); + return element; + }, + visualEffect: function(element, effect, options) { + element = $(element); + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[klass](element, options); + return element; + }, + highlight: function(element, options) { + element = $(element); + new Effect.Highlight(element, options); + return element; + } +}; + +$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ + 'pulsate shake puff squish switchOff dropOut').each( + function(effect) { + Effect.Methods[effect] = function(element, options){ + element = $(element); + Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); + return element; + }; + } +); + +$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( + function(f) { Effect.Methods[f] = Element[f]; } +); + +Element.addMethods(Effect.Methods); \ No newline at end of file diff --git a/recipebook/public/javascripts/jquery-ui.js b/recipebook/public/javascripts/jquery-ui.js new file mode 100644 index 00000000000..d9f0fc76565 --- /dev/null +++ b/recipebook/public/javascripts/jquery-ui.js @@ -0,0 +1,11577 @@ +/*! + * jQuery UI 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI + */ +(function( $, undefined ) { + +// prevent duplicate loading +// this is only a problem because we proxy existing functions +// and we don't want to double proxy them +$.ui = $.ui || {}; +if ( $.ui.version ) { + return; +} + +$.extend( $.ui, { + version: "1.8.11", + + keyCode: { + ALT: 18, + BACKSPACE: 8, + CAPS_LOCK: 20, + COMMA: 188, + COMMAND: 91, + COMMAND_LEFT: 91, // COMMAND + COMMAND_RIGHT: 93, + CONTROL: 17, + DELETE: 46, + DOWN: 40, + END: 35, + ENTER: 13, + ESCAPE: 27, + HOME: 36, + INSERT: 45, + LEFT: 37, + MENU: 93, // COMMAND_RIGHT + NUMPAD_ADD: 107, + NUMPAD_DECIMAL: 110, + NUMPAD_DIVIDE: 111, + NUMPAD_ENTER: 108, + NUMPAD_MULTIPLY: 106, + NUMPAD_SUBTRACT: 109, + PAGE_DOWN: 34, + PAGE_UP: 33, + PERIOD: 190, + RIGHT: 39, + SHIFT: 16, + SPACE: 32, + TAB: 9, + UP: 38, + WINDOWS: 91 // COMMAND + } +}); + +// plugins +$.fn.extend({ + _focus: $.fn.focus, + focus: function( delay, fn ) { + return typeof delay === "number" ? + this.each(function() { + var elem = this; + setTimeout(function() { + $( elem ).focus(); + if ( fn ) { + fn.call( elem ); + } + }, delay ); + }) : + this._focus.apply( this, arguments ); + }, + + scrollParent: function() { + var scrollParent; + if (($.browser.msie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) { + scrollParent = this.parents().filter(function() { + return (/(relative|absolute|fixed)/).test($.curCSS(this,'position',1)) && (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1)); + }).eq(0); + } else { + scrollParent = this.parents().filter(function() { + return (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1)); + }).eq(0); + } + + return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent; + }, + + zIndex: function( zIndex ) { + if ( zIndex !== undefined ) { + return this.css( "zIndex", zIndex ); + } + + if ( this.length ) { + var elem = $( this[ 0 ] ), position, value; + while ( elem.length && elem[ 0 ] !== document ) { + // Ignore z-index if position is set to a value where z-index is ignored by the browser + // This makes behavior of this function consistent across browsers + // WebKit always returns auto if the element is positioned + position = elem.css( "position" ); + if ( position === "absolute" || position === "relative" || position === "fixed" ) { + // IE returns 0 when zIndex is not specified + // other browsers return a string + // we ignore the case of nested elements with an explicit value of 0 + //
    + value = parseInt( elem.css( "zIndex" ), 10 ); + if ( !isNaN( value ) && value !== 0 ) { + return value; + } + } + elem = elem.parent(); + } + } + + return 0; + }, + + disableSelection: function() { + return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + + ".ui-disableSelection", function( event ) { + event.preventDefault(); + }); + }, + + enableSelection: function() { + return this.unbind( ".ui-disableSelection" ); + } +}); + +$.each( [ "Width", "Height" ], function( i, name ) { + var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], + type = name.toLowerCase(), + orig = { + innerWidth: $.fn.innerWidth, + innerHeight: $.fn.innerHeight, + outerWidth: $.fn.outerWidth, + outerHeight: $.fn.outerHeight + }; + + function reduce( elem, size, border, margin ) { + $.each( side, function() { + size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0; + if ( border ) { + size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0; + } + if ( margin ) { + size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0; + } + }); + return size; + } + + $.fn[ "inner" + name ] = function( size ) { + if ( size === undefined ) { + return orig[ "inner" + name ].call( this ); + } + + return this.each(function() { + $( this ).css( type, reduce( this, size ) + "px" ); + }); + }; + + $.fn[ "outer" + name] = function( size, margin ) { + if ( typeof size !== "number" ) { + return orig[ "outer" + name ].call( this, size ); + } + + return this.each(function() { + $( this).css( type, reduce( this, size, true, margin ) + "px" ); + }); + }; +}); + +// selectors +function visible( element ) { + return !$( element ).parents().andSelf().filter(function() { + return $.curCSS( this, "visibility" ) === "hidden" || + $.expr.filters.hidden( this ); + }).length; +} + +$.extend( $.expr[ ":" ], { + data: function( elem, i, match ) { + return !!$.data( elem, match[ 3 ] ); + }, + + focusable: function( element ) { + var nodeName = element.nodeName.toLowerCase(), + tabIndex = $.attr( element, "tabindex" ); + if ( "area" === nodeName ) { + var map = element.parentNode, + mapName = map.name, + img; + if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { + return false; + } + img = $( "img[usemap=#" + mapName + "]" )[0]; + return !!img && visible( img ); + } + return ( /input|select|textarea|button|object/.test( nodeName ) + ? !element.disabled + : "a" == nodeName + ? element.href || !isNaN( tabIndex ) + : !isNaN( tabIndex )) + // the element and all of its ancestors must be visible + && visible( element ); + }, + + tabbable: function( element ) { + var tabIndex = $.attr( element, "tabindex" ); + return ( isNaN( tabIndex ) || tabIndex >= 0 ) && $( element ).is( ":focusable" ); + } +}); + +// support +$(function() { + var body = document.body, + div = body.appendChild( div = document.createElement( "div" ) ); + + $.extend( div.style, { + minHeight: "100px", + height: "auto", + padding: 0, + borderWidth: 0 + }); + + $.support.minHeight = div.offsetHeight === 100; + $.support.selectstart = "onselectstart" in div; + + // set display to none to avoid a layout bug in IE + // http://dev.jquery.com/ticket/4014 + body.removeChild( div ).style.display = "none"; +}); + + + + + +// deprecated +$.extend( $.ui, { + // $.ui.plugin is deprecated. Use the proxy pattern instead. + plugin: { + add: function( module, option, set ) { + var proto = $.ui[ module ].prototype; + for ( var i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args ) { + var set = instance.plugins[ name ]; + if ( !set || !instance.element[ 0 ].parentNode ) { + return; + } + + for ( var i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } + } + }, + + // will be deprecated when we switch to jQuery 1.4 - use jQuery.contains() + contains: function( a, b ) { + return document.compareDocumentPosition ? + a.compareDocumentPosition( b ) & 16 : + a !== b && a.contains( b ); + }, + + // only used by resizable + hasScroll: function( el, a ) { + + //If overflow is hidden, the element might have extra content, but the user wants to hide it + if ( $( el ).css( "overflow" ) === "hidden") { + return false; + } + + var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", + has = false; + + if ( el[ scroll ] > 0 ) { + return true; + } + + // TODO: determine which cases actually cause this to happen + // if the element doesn't have the scroll set, see if it's possible to + // set the scroll + el[ scroll ] = 1; + has = ( el[ scroll ] > 0 ); + el[ scroll ] = 0; + return has; + }, + + // these are odd functions, fix the API or move into individual plugins + isOverAxis: function( x, reference, size ) { + //Determines when x coordinate is over "b" element axis + return ( x > reference ) && ( x < ( reference + size ) ); + }, + isOver: function( y, x, top, left, height, width ) { + //Determines when x, y coordinates is over "b" element + return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width ); + } +}); + +})( jQuery ); +/*! + * jQuery UI Widget 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Widget + */ +(function( $, undefined ) { + +// jQuery 1.4+ +if ( $.cleanData ) { + var _cleanData = $.cleanData; + $.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + $( elem ).triggerHandler( "remove" ); + } + _cleanData( elems ); + }; +} else { + var _remove = $.fn.remove; + $.fn.remove = function( selector, keepData ) { + return this.each(function() { + if ( !keepData ) { + if ( !selector || $.filter( selector, [ this ] ).length ) { + $( "*", this ).add( [ this ] ).each(function() { + $( this ).triggerHandler( "remove" ); + }); + } + } + return _remove.call( $(this), selector, keepData ); + }); + }; +} + +$.widget = function( name, base, prototype ) { + var namespace = name.split( "." )[ 0 ], + fullName; + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName ] = function( elem ) { + return !!$.data( elem, name ); + }; + + $[ namespace ] = $[ namespace ] || {}; + $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without initializing for simple inheritance + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + + var basePrototype = new base(); + // we need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from +// $.each( basePrototype, function( key, val ) { +// if ( $.isPlainObject(val) ) { +// basePrototype[ key ] = $.extend( {}, val ); +// } +// }); + basePrototype.options = $.extend( true, {}, basePrototype.options ); + $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { + namespace: namespace, + widgetName: name, + widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, + widgetBaseClass: fullName + }, prototype ); + + $.widget.bridge( name, $[ namespace ][ name ] ); +}; + +$.widget.bridge = function( name, object ) { + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = Array.prototype.slice.call( arguments, 1 ), + returnValue = this; + + // allow multiple hashes to be passed on init + options = !isMethodCall && args.length ? + $.extend.apply( null, [ true, options ].concat(args) ) : + options; + + // prevent calls to internal methods + if ( isMethodCall && options.charAt( 0 ) === "_" ) { + return returnValue; + } + + if ( isMethodCall ) { + this.each(function() { + var instance = $.data( this, name ), + methodValue = instance && $.isFunction( instance[options] ) ? + instance[ options ].apply( instance, args ) : + instance; + // TODO: add this back in 1.9 and use $.error() (see #5972) +// if ( !instance ) { +// throw "cannot call methods on " + name + " prior to initialization; " + +// "attempted to call method '" + options + "'"; +// } +// if ( !$.isFunction( instance[options] ) ) { +// throw "no such method '" + options + "' for " + name + " widget instance"; +// } +// var methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue; + return false; + } + }); + } else { + this.each(function() { + var instance = $.data( this, name ); + if ( instance ) { + instance.option( options || {} )._init(); + } else { + $.data( this, name, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( options, element ) { + // allow instantiation without initializing for simple inheritance + if ( arguments.length ) { + this._createWidget( options, element ); + } +}; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + options: { + disabled: false + }, + _createWidget: function( options, element ) { + // $.widget.bridge stores the plugin instance, but we do it anyway + // so that it's stored even before the _create function runs + $.data( element, this.widgetName, this ); + this.element = $( element ); + this.options = $.extend( true, {}, + this.options, + this._getCreateOptions(), + options ); + + var self = this; + this.element.bind( "remove." + this.widgetName, function() { + self.destroy(); + }); + + this._create(); + this._trigger( "create" ); + this._init(); + }, + _getCreateOptions: function() { + return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; + }, + _create: function() {}, + _init: function() {}, + + destroy: function() { + this.element + .unbind( "." + this.widgetName ) + .removeData( this.widgetName ); + this.widget() + .unbind( "." + this.widgetName ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetBaseClass + "-disabled " + + "ui-state-disabled" ); + }, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.extend( {}, this.options ); + } + + if (typeof key === "string" ) { + if ( value === undefined ) { + return this.options[ key ]; + } + options = {}; + options[ key ] = value; + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var self = this; + $.each( options, function( key, value ) { + self._setOption( key, value ); + }); + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + [ value ? "addClass" : "removeClass"]( + this.widgetBaseClass + "-disabled" + " " + + "ui-state-disabled" ) + .attr( "aria-disabled", value ); + } + + return this; + }, + + enable: function() { + return this._setOption( "disabled", false ); + }, + disable: function() { + return this._setOption( "disabled", true ); + }, + + _trigger: function( type, event, data ) { + var callback = this.options[ type ]; + + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + data = data || {}; + + // copy original event properties over to the new event + // this would happen if we could call $.event.fix instead of $.Event + // but we don't have a way to force an event to be fixed multiple times + if ( event.originalEvent ) { + for ( var i = $.event.props.length, prop; i; ) { + prop = $.event.props[ --i ]; + event[ prop ] = event.originalEvent[ prop ]; + } + } + + this.element.trigger( event, data ); + + return !( $.isFunction(callback) && + callback.call( this.element[0], event, data ) === false || + event.isDefaultPrevented() ); + } +}; + +})( jQuery ); +/*! + * jQuery UI Mouse 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Mouse + * + * Depends: + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +$.widget("ui.mouse", { + options: { + cancel: ':input,option', + distance: 1, + delay: 0 + }, + _mouseInit: function() { + var self = this; + + this.element + .bind('mousedown.'+this.widgetName, function(event) { + return self._mouseDown(event); + }) + .bind('click.'+this.widgetName, function(event) { + if (true === $.data(event.target, self.widgetName + '.preventClickEvent')) { + $.removeData(event.target, self.widgetName + '.preventClickEvent'); + event.stopImmediatePropagation(); + return false; + } + }); + + this.started = false; + }, + + // TODO: make sure destroying one instance of mouse doesn't mess with + // other instances of mouse + _mouseDestroy: function() { + this.element.unbind('.'+this.widgetName); + }, + + _mouseDown: function(event) { + // don't let more than one widget handle mouseStart + // TODO: figure out why we have to use originalEvent + event.originalEvent = event.originalEvent || {}; + if (event.originalEvent.mouseHandled) { return; } + + // we may have missed mouseup (out of window) + (this._mouseStarted && this._mouseUp(event)); + + this._mouseDownEvent = event; + + var self = this, + btnIsLeft = (event.which == 1), + elIsCancel = (typeof this.options.cancel == "string" ? $(event.target).parents().add(event.target).filter(this.options.cancel).length : false); + if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { + return true; + } + + this.mouseDelayMet = !this.options.delay; + if (!this.mouseDelayMet) { + this._mouseDelayTimer = setTimeout(function() { + self.mouseDelayMet = true; + }, this.options.delay); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = (this._mouseStart(event) !== false); + if (!this._mouseStarted) { + event.preventDefault(); + return true; + } + } + + // Click event may never have fired (Gecko & Opera) + if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) { + $.removeData(event.target, this.widgetName + '.preventClickEvent'); + } + + // these delegates are required to keep context + this._mouseMoveDelegate = function(event) { + return self._mouseMove(event); + }; + this._mouseUpDelegate = function(event) { + return self._mouseUp(event); + }; + $(document) + .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate) + .bind('mouseup.'+this.widgetName, this._mouseUpDelegate); + + event.preventDefault(); + event.originalEvent.mouseHandled = true; + return true; + }, + + _mouseMove: function(event) { + // IE mouseup check - mouseup happened when mouse was out of window + if ($.browser.msie && !(document.documentMode >= 9) && !event.button) { + return this._mouseUp(event); + } + + if (this._mouseStarted) { + this._mouseDrag(event); + return event.preventDefault(); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = + (this._mouseStart(this._mouseDownEvent, event) !== false); + (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); + } + + return !this._mouseStarted; + }, + + _mouseUp: function(event) { + $(document) + .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate) + .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate); + + if (this._mouseStarted) { + this._mouseStarted = false; + + if (event.target == this._mouseDownEvent.target) { + $.data(event.target, this.widgetName + '.preventClickEvent', true); + } + + this._mouseStop(event); + } + + return false; + }, + + _mouseDistanceMet: function(event) { + return (Math.max( + Math.abs(this._mouseDownEvent.pageX - event.pageX), + Math.abs(this._mouseDownEvent.pageY - event.pageY) + ) >= this.options.distance + ); + }, + + _mouseDelayMet: function(event) { + return this.mouseDelayMet; + }, + + // These are placeholder methods, to be overriden by extending plugin + _mouseStart: function(event) {}, + _mouseDrag: function(event) {}, + _mouseStop: function(event) {}, + _mouseCapture: function(event) { return true; } +}); + +})(jQuery); +/* + * jQuery UI Draggable 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Draggables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +$.widget("ui.draggable", $.ui.mouse, { + widgetEventPrefix: "drag", + options: { + addClasses: true, + appendTo: "parent", + axis: false, + connectToSortable: false, + containment: false, + cursor: "auto", + cursorAt: false, + grid: false, + handle: false, + helper: "original", + iframeFix: false, + opacity: false, + refreshPositions: false, + revert: false, + revertDuration: 500, + scope: "default", + scroll: true, + scrollSensitivity: 20, + scrollSpeed: 20, + snap: false, + snapMode: "both", + snapTolerance: 20, + stack: false, + zIndex: false + }, + _create: function() { + + if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position"))) + this.element[0].style.position = 'relative'; + + (this.options.addClasses && this.element.addClass("ui-draggable")); + (this.options.disabled && this.element.addClass("ui-draggable-disabled")); + + this._mouseInit(); + + }, + + destroy: function() { + if(!this.element.data('draggable')) return; + this.element + .removeData("draggable") + .unbind(".draggable") + .removeClass("ui-draggable" + + " ui-draggable-dragging" + + " ui-draggable-disabled"); + this._mouseDestroy(); + + return this; + }, + + _mouseCapture: function(event) { + + var o = this.options; + + // among others, prevent a drag on a resizable-handle + if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle')) + return false; + + //Quit if we're not on a valid handle + this.handle = this._getHandle(event); + if (!this.handle) + return false; + + return true; + + }, + + _mouseStart: function(event) { + + var o = this.options; + + //Create and append the visible helper + this.helper = this._createHelper(event); + + //Cache the helper size + this._cacheHelperProportions(); + + //If ddmanager is used for droppables, set the global draggable + if($.ui.ddmanager) + $.ui.ddmanager.current = this; + + /* + * - Position generation - + * This block generates everything position related - it's the core of draggables. + */ + + //Cache the margins of the original element + this._cacheMargins(); + + //Store the helper's css position + this.cssPosition = this.helper.css("position"); + this.scrollParent = this.helper.scrollParent(); + + //The element's absolute position on the page minus margins + this.offset = this.positionAbs = this.element.offset(); + this.offset = { + top: this.offset.top - this.margins.top, + left: this.offset.left - this.margins.left + }; + + $.extend(this.offset, { + click: { //Where the click happened, relative to the element + left: event.pageX - this.offset.left, + top: event.pageY - this.offset.top + }, + parent: this._getParentOffset(), + relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper + }); + + //Generate the original position + this.originalPosition = this.position = this._generatePosition(event); + this.originalPageX = event.pageX; + this.originalPageY = event.pageY; + + //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied + (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); + + //Set a containment if given in the options + if(o.containment) + this._setContainment(); + + //Trigger event + callbacks + if(this._trigger("start", event) === false) { + this._clear(); + return false; + } + + //Recache the helper size + this._cacheHelperProportions(); + + //Prepare the droppable offsets + if ($.ui.ddmanager && !o.dropBehaviour) + $.ui.ddmanager.prepareOffsets(this, event); + + this.helper.addClass("ui-draggable-dragging"); + this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position + return true; + }, + + _mouseDrag: function(event, noPropagation) { + + //Compute the helpers position + this.position = this._generatePosition(event); + this.positionAbs = this._convertPositionTo("absolute"); + + //Call plugins and callbacks and use the resulting position if something is returned + if (!noPropagation) { + var ui = this._uiHash(); + if(this._trigger('drag', event, ui) === false) { + this._mouseUp({}); + return false; + } + this.position = ui.position; + } + + if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px'; + if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px'; + if($.ui.ddmanager) $.ui.ddmanager.drag(this, event); + + return false; + }, + + _mouseStop: function(event) { + + //If we are using droppables, inform the manager about the drop + var dropped = false; + if ($.ui.ddmanager && !this.options.dropBehaviour) + dropped = $.ui.ddmanager.drop(this, event); + + //if a drop comes from outside (a sortable) + if(this.dropped) { + dropped = this.dropped; + this.dropped = false; + } + + //if the original element is removed, don't bother to continue if helper is set to "original" + if((!this.element[0] || !this.element[0].parentNode) && this.options.helper == "original") + return false; + + if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) { + var self = this; + $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() { + if(self._trigger("stop", event) !== false) { + self._clear(); + } + }); + } else { + if(this._trigger("stop", event) !== false) { + this._clear(); + } + } + + return false; + }, + + cancel: function() { + + if(this.helper.is(".ui-draggable-dragging")) { + this._mouseUp({}); + } else { + this._clear(); + } + + return this; + + }, + + _getHandle: function(event) { + + var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false; + $(this.options.handle, this.element) + .find("*") + .andSelf() + .each(function() { + if(this == event.target) handle = true; + }); + + return handle; + + }, + + _createHelper: function(event) { + + var o = this.options; + var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone() : this.element); + + if(!helper.parents('body').length) + helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo)); + + if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) + helper.css("position", "absolute"); + + return helper; + + }, + + _adjustOffsetFromHelper: function(obj) { + if (typeof obj == 'string') { + obj = obj.split(' '); + } + if ($.isArray(obj)) { + obj = {left: +obj[0], top: +obj[1] || 0}; + } + if ('left' in obj) { + this.offset.click.left = obj.left + this.margins.left; + } + if ('right' in obj) { + this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; + } + if ('top' in obj) { + this.offset.click.top = obj.top + this.margins.top; + } + if ('bottom' in obj) { + this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; + } + }, + + _getParentOffset: function() { + + //Get the offsetParent and cache its position + this.offsetParent = this.helper.offsetParent(); + var po = this.offsetParent.offset(); + + // This is a special case where we need to modify a offset calculated on start, since the following happened: + // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent + // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that + // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag + if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) { + po.left += this.scrollParent.scrollLeft(); + po.top += this.scrollParent.scrollTop(); + } + + if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information + || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix + po = { top: 0, left: 0 }; + + return { + top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), + left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) + }; + + }, + + _getRelativeOffset: function() { + + if(this.cssPosition == "relative") { + var p = this.element.position(); + return { + top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), + left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() + }; + } else { + return { top: 0, left: 0 }; + } + + }, + + _cacheMargins: function() { + this.margins = { + left: (parseInt(this.element.css("marginLeft"),10) || 0), + top: (parseInt(this.element.css("marginTop"),10) || 0), + right: (parseInt(this.element.css("marginRight"),10) || 0), + bottom: (parseInt(this.element.css("marginBottom"),10) || 0) + }; + }, + + _cacheHelperProportions: function() { + this.helperProportions = { + width: this.helper.outerWidth(), + height: this.helper.outerHeight() + }; + }, + + _setContainment: function() { + + var o = this.options; + if(o.containment == 'parent') o.containment = this.helper[0].parentNode; + if(o.containment == 'document' || o.containment == 'window') this.containment = [ + (o.containment == 'document' ? 0 : $(window).scrollLeft()) - this.offset.relative.left - this.offset.parent.left, + (o.containment == 'document' ? 0 : $(window).scrollTop()) - this.offset.relative.top - this.offset.parent.top, + (o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left, + (o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top + ]; + + if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) { + var ce = $(o.containment)[0]; if(!ce) return; + var co = $(o.containment).offset(); + var over = ($(ce).css("overflow") != 'hidden'); + + this.containment = [ + co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0), + co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0), + co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right, + co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - this.margins.bottom + ]; + } else if(o.containment.constructor == Array) { + this.containment = o.containment; + } + + }, + + _convertPositionTo: function(d, pos) { + + if(!pos) pos = this.position; + var mod = d == "absolute" ? 1 : -1; + var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); + + return { + top: ( + pos.top // The absolute mouse position + + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent + + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border) + - ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) + ), + left: ( + pos.left // The absolute mouse position + + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent + + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border) + - ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) + ) + }; + + }, + + _generatePosition: function(event) { + + var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); + var pageX = event.pageX; + var pageY = event.pageY; + + /* + * - Position constraining - + * Constrain the position to a mix of grid, containment. + */ + + if(this.originalPosition) { //If we are not dragging yet, we won't check for options + + if(this.containment) { + if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left; + if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top; + if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left; + if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top; + } + + if(o.grid) { + var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1]; + pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; + + var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0]; + pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; + } + + } + + return { + top: ( + pageY // The absolute mouse position + - this.offset.click.top // Click offset (relative to the element) + - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent + - this.offset.parent.top // The offsetParent's offset without borders (offset + border) + + ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) + ), + left: ( + pageX // The absolute mouse position + - this.offset.click.left // Click offset (relative to the element) + - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent + - this.offset.parent.left // The offsetParent's offset without borders (offset + border) + + ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) + ) + }; + + }, + + _clear: function() { + this.helper.removeClass("ui-draggable-dragging"); + if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove(); + //if($.ui.ddmanager) $.ui.ddmanager.current = null; + this.helper = null; + this.cancelHelperRemoval = false; + }, + + // From now on bulk stuff - mainly helpers + + _trigger: function(type, event, ui) { + ui = ui || this._uiHash(); + $.ui.plugin.call(this, type, [event, ui]); + if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins + return $.Widget.prototype._trigger.call(this, type, event, ui); + }, + + plugins: {}, + + _uiHash: function(event) { + return { + helper: this.helper, + position: this.position, + originalPosition: this.originalPosition, + offset: this.positionAbs + }; + } + +}); + +$.extend($.ui.draggable, { + version: "1.8.11" +}); + +$.ui.plugin.add("draggable", "connectToSortable", { + start: function(event, ui) { + + var inst = $(this).data("draggable"), o = inst.options, + uiSortable = $.extend({}, ui, { item: inst.element }); + inst.sortables = []; + $(o.connectToSortable).each(function() { + var sortable = $.data(this, 'sortable'); + if (sortable && !sortable.options.disabled) { + inst.sortables.push({ + instance: sortable, + shouldRevert: sortable.options.revert + }); + sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page). + sortable._trigger("activate", event, uiSortable); + } + }); + + }, + stop: function(event, ui) { + + //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper + var inst = $(this).data("draggable"), + uiSortable = $.extend({}, ui, { item: inst.element }); + + $.each(inst.sortables, function() { + if(this.instance.isOver) { + + this.instance.isOver = 0; + + inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance + this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work) + + //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid' + if(this.shouldRevert) this.instance.options.revert = true; + + //Trigger the stop of the sortable + this.instance._mouseStop(event); + + this.instance.options.helper = this.instance.options._helper; + + //If the helper has been the original item, restore properties in the sortable + if(inst.options.helper == 'original') + this.instance.currentItem.css({ top: 'auto', left: 'auto' }); + + } else { + this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance + this.instance._trigger("deactivate", event, uiSortable); + } + + }); + + }, + drag: function(event, ui) { + + var inst = $(this).data("draggable"), self = this; + + var checkPos = function(o) { + var dyClick = this.offset.click.top, dxClick = this.offset.click.left; + var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left; + var itemHeight = o.height, itemWidth = o.width; + var itemTop = o.top, itemLeft = o.left; + + return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth); + }; + + $.each(inst.sortables, function(i) { + + //Copy over some variables to allow calling the sortable's native _intersectsWith + this.instance.positionAbs = inst.positionAbs; + this.instance.helperProportions = inst.helperProportions; + this.instance.offset.click = inst.offset.click; + + if(this.instance._intersectsWith(this.instance.containerCache)) { + + //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once + if(!this.instance.isOver) { + + this.instance.isOver = 1; + //Now we fake the start of dragging for the sortable instance, + //by cloning the list group item, appending it to the sortable and using it as inst.currentItem + //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one) + this.instance.currentItem = $(self).clone().appendTo(this.instance.element).data("sortable-item", true); + this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it + this.instance.options.helper = function() { return ui.helper[0]; }; + + event.target = this.instance.currentItem[0]; + this.instance._mouseCapture(event, true); + this.instance._mouseStart(event, true, true); + + //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes + this.instance.offset.click.top = inst.offset.click.top; + this.instance.offset.click.left = inst.offset.click.left; + this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left; + this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top; + + inst._trigger("toSortable", event); + inst.dropped = this.instance.element; //draggable revert needs that + //hack so receive/update callbacks work (mostly) + inst.currentItem = inst.element; + this.instance.fromOutside = inst; + + } + + //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable + if(this.instance.currentItem) this.instance._mouseDrag(event); + + } else { + + //If it doesn't intersect with the sortable, and it intersected before, + //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval + if(this.instance.isOver) { + + this.instance.isOver = 0; + this.instance.cancelHelperRemoval = true; + + //Prevent reverting on this forced stop + this.instance.options.revert = false; + + // The out event needs to be triggered independently + this.instance._trigger('out', event, this.instance._uiHash(this.instance)); + + this.instance._mouseStop(event, true); + this.instance.options.helper = this.instance.options._helper; + + //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size + this.instance.currentItem.remove(); + if(this.instance.placeholder) this.instance.placeholder.remove(); + + inst._trigger("fromSortable", event); + inst.dropped = false; //draggable revert needs that + } + + }; + + }); + + } +}); + +$.ui.plugin.add("draggable", "cursor", { + start: function(event, ui) { + var t = $('body'), o = $(this).data('draggable').options; + if (t.css("cursor")) o._cursor = t.css("cursor"); + t.css("cursor", o.cursor); + }, + stop: function(event, ui) { + var o = $(this).data('draggable').options; + if (o._cursor) $('body').css("cursor", o._cursor); + } +}); + +$.ui.plugin.add("draggable", "iframeFix", { + start: function(event, ui) { + var o = $(this).data('draggable').options; + $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() { + $('
    ') + .css({ + width: this.offsetWidth+"px", height: this.offsetHeight+"px", + position: "absolute", opacity: "0.001", zIndex: 1000 + }) + .css($(this).offset()) + .appendTo("body"); + }); + }, + stop: function(event, ui) { + $("div.ui-draggable-iframeFix").each(function() { this.parentNode.removeChild(this); }); //Remove frame helpers + } +}); + +$.ui.plugin.add("draggable", "opacity", { + start: function(event, ui) { + var t = $(ui.helper), o = $(this).data('draggable').options; + if(t.css("opacity")) o._opacity = t.css("opacity"); + t.css('opacity', o.opacity); + }, + stop: function(event, ui) { + var o = $(this).data('draggable').options; + if(o._opacity) $(ui.helper).css('opacity', o._opacity); + } +}); + +$.ui.plugin.add("draggable", "scroll", { + start: function(event, ui) { + var i = $(this).data("draggable"); + if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset(); + }, + drag: function(event, ui) { + + var i = $(this).data("draggable"), o = i.options, scrolled = false; + + if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') { + + if(!o.axis || o.axis != 'x') { + if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) + i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed; + else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) + i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed; + } + + if(!o.axis || o.axis != 'y') { + if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) + i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed; + else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) + i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed; + } + + } else { + + if(!o.axis || o.axis != 'x') { + if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) + scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); + else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) + scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); + } + + if(!o.axis || o.axis != 'y') { + if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) + scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); + else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) + scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); + } + + } + + if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) + $.ui.ddmanager.prepareOffsets(i, event); + + } +}); + +$.ui.plugin.add("draggable", "snap", { + start: function(event, ui) { + + var i = $(this).data("draggable"), o = i.options; + i.snapElements = []; + + $(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() { + var $t = $(this); var $o = $t.offset(); + if(this != i.element[0]) i.snapElements.push({ + item: this, + width: $t.outerWidth(), height: $t.outerHeight(), + top: $o.top, left: $o.left + }); + }); + + }, + drag: function(event, ui) { + + var inst = $(this).data("draggable"), o = inst.options; + var d = o.snapTolerance; + + var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width, + y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height; + + for (var i = inst.snapElements.length - 1; i >= 0; i--){ + + var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width, + t = inst.snapElements[i].top, b = t + inst.snapElements[i].height; + + //Yes, I know, this is insane ;) + if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) { + if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); + inst.snapElements[i].snapping = false; + continue; + } + + if(o.snapMode != 'inner') { + var ts = Math.abs(t - y2) <= d; + var bs = Math.abs(b - y1) <= d; + var ls = Math.abs(l - x2) <= d; + var rs = Math.abs(r - x1) <= d; + if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top; + if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top; + if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left; + if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left; + } + + var first = (ts || bs || ls || rs); + + if(o.snapMode != 'outer') { + var ts = Math.abs(t - y1) <= d; + var bs = Math.abs(b - y2) <= d; + var ls = Math.abs(l - x1) <= d; + var rs = Math.abs(r - x2) <= d; + if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top; + if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top; + if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left; + if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left; + } + + if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) + (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); + inst.snapElements[i].snapping = (ts || bs || ls || rs || first); + + }; + + } +}); + +$.ui.plugin.add("draggable", "stack", { + start: function(event, ui) { + + var o = $(this).data("draggable").options; + + var group = $.makeArray($(o.stack)).sort(function(a,b) { + return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0); + }); + if (!group.length) { return; } + + var min = parseInt(group[0].style.zIndex) || 0; + $(group).each(function(i) { + this.style.zIndex = min + i; + }); + + this[0].style.zIndex = min + group.length; + + } +}); + +$.ui.plugin.add("draggable", "zIndex", { + start: function(event, ui) { + var t = $(ui.helper), o = $(this).data("draggable").options; + if(t.css("zIndex")) o._zIndex = t.css("zIndex"); + t.css('zIndex', o.zIndex); + }, + stop: function(event, ui) { + var o = $(this).data("draggable").options; + if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex); + } +}); + +})(jQuery); +/* + * jQuery UI Droppable 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Droppables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.mouse.js + * jquery.ui.draggable.js + */ +(function( $, undefined ) { + +$.widget("ui.droppable", { + widgetEventPrefix: "drop", + options: { + accept: '*', + activeClass: false, + addClasses: true, + greedy: false, + hoverClass: false, + scope: 'default', + tolerance: 'intersect' + }, + _create: function() { + + var o = this.options, accept = o.accept; + this.isover = 0; this.isout = 1; + + this.accept = $.isFunction(accept) ? accept : function(d) { + return d.is(accept); + }; + + //Store the droppable's proportions + this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight }; + + // Add the reference and positions to the manager + $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || []; + $.ui.ddmanager.droppables[o.scope].push(this); + + (o.addClasses && this.element.addClass("ui-droppable")); + + }, + + destroy: function() { + var drop = $.ui.ddmanager.droppables[this.options.scope]; + for ( var i = 0; i < drop.length; i++ ) + if ( drop[i] == this ) + drop.splice(i, 1); + + this.element + .removeClass("ui-droppable ui-droppable-disabled") + .removeData("droppable") + .unbind(".droppable"); + + return this; + }, + + _setOption: function(key, value) { + + if(key == 'accept') { + this.accept = $.isFunction(value) ? value : function(d) { + return d.is(value); + }; + } + $.Widget.prototype._setOption.apply(this, arguments); + }, + + _activate: function(event) { + var draggable = $.ui.ddmanager.current; + if(this.options.activeClass) this.element.addClass(this.options.activeClass); + (draggable && this._trigger('activate', event, this.ui(draggable))); + }, + + _deactivate: function(event) { + var draggable = $.ui.ddmanager.current; + if(this.options.activeClass) this.element.removeClass(this.options.activeClass); + (draggable && this._trigger('deactivate', event, this.ui(draggable))); + }, + + _over: function(event) { + + var draggable = $.ui.ddmanager.current; + if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element + + if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { + if(this.options.hoverClass) this.element.addClass(this.options.hoverClass); + this._trigger('over', event, this.ui(draggable)); + } + + }, + + _out: function(event) { + + var draggable = $.ui.ddmanager.current; + if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element + + if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { + if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass); + this._trigger('out', event, this.ui(draggable)); + } + + }, + + _drop: function(event,custom) { + + var draggable = custom || $.ui.ddmanager.current; + if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element + + var childrenIntersection = false; + this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() { + var inst = $.data(this, 'droppable'); + if( + inst.options.greedy + && !inst.options.disabled + && inst.options.scope == draggable.options.scope + && inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) + && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance) + ) { childrenIntersection = true; return false; } + }); + if(childrenIntersection) return false; + + if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { + if(this.options.activeClass) this.element.removeClass(this.options.activeClass); + if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass); + this._trigger('drop', event, this.ui(draggable)); + return this.element; + } + + return false; + + }, + + ui: function(c) { + return { + draggable: (c.currentItem || c.element), + helper: c.helper, + position: c.position, + offset: c.positionAbs + }; + } + +}); + +$.extend($.ui.droppable, { + version: "1.8.11" +}); + +$.ui.intersect = function(draggable, droppable, toleranceMode) { + + if (!droppable.offset) return false; + + var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width, + y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height; + var l = droppable.offset.left, r = l + droppable.proportions.width, + t = droppable.offset.top, b = t + droppable.proportions.height; + + switch (toleranceMode) { + case 'fit': + return (l <= x1 && x2 <= r + && t <= y1 && y2 <= b); + break; + case 'intersect': + return (l < x1 + (draggable.helperProportions.width / 2) // Right Half + && x2 - (draggable.helperProportions.width / 2) < r // Left Half + && t < y1 + (draggable.helperProportions.height / 2) // Bottom Half + && y2 - (draggable.helperProportions.height / 2) < b ); // Top Half + break; + case 'pointer': + var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left), + draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top), + isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width); + return isOver; + break; + case 'touch': + return ( + (y1 >= t && y1 <= b) || // Top edge touching + (y2 >= t && y2 <= b) || // Bottom edge touching + (y1 < t && y2 > b) // Surrounded vertically + ) && ( + (x1 >= l && x1 <= r) || // Left edge touching + (x2 >= l && x2 <= r) || // Right edge touching + (x1 < l && x2 > r) // Surrounded horizontally + ); + break; + default: + return false; + break; + } + +}; + +/* + This manager tracks offsets of draggables and droppables +*/ +$.ui.ddmanager = { + current: null, + droppables: { 'default': [] }, + prepareOffsets: function(t, event) { + + var m = $.ui.ddmanager.droppables[t.options.scope] || []; + var type = event ? event.type : null; // workaround for #2317 + var list = (t.currentItem || t.element).find(":data(droppable)").andSelf(); + + droppablesLoop: for (var i = 0; i < m.length; i++) { + + if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue; //No disabled and non-accepted + for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item + m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; //If the element is not visible, continue + + if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables + + m[i].offset = m[i].element.offset(); + m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight }; + + } + + }, + drop: function(draggable, event) { + + var dropped = false; + $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { + + if(!this.options) return; + if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) + dropped = dropped || this._drop.call(this, event); + + if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { + this.isout = 1; this.isover = 0; + this._deactivate.call(this, event); + } + + }); + return dropped; + + }, + drag: function(draggable, event) { + + //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse. + if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event); + + //Run through all droppables and check their positions based on specific tolerance options + $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { + + if(this.options.disabled || this.greedyChild || !this.visible) return; + var intersects = $.ui.intersect(draggable, this, this.options.tolerance); + + var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null); + if(!c) return; + + var parentInstance; + if (this.options.greedy) { + var parent = this.element.parents(':data(droppable):eq(0)'); + if (parent.length) { + parentInstance = $.data(parent[0], 'droppable'); + parentInstance.greedyChild = (c == 'isover' ? 1 : 0); + } + } + + // we just moved into a greedy child + if (parentInstance && c == 'isover') { + parentInstance['isover'] = 0; + parentInstance['isout'] = 1; + parentInstance._out.call(parentInstance, event); + } + + this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0; + this[c == "isover" ? "_over" : "_out"].call(this, event); + + // we just moved out of a greedy child + if (parentInstance && c == 'isout') { + parentInstance['isout'] = 0; + parentInstance['isover'] = 1; + parentInstance._over.call(parentInstance, event); + } + }); + + } +}; + +})(jQuery); +/* + * jQuery UI Resizable 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Resizables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +$.widget("ui.resizable", $.ui.mouse, { + widgetEventPrefix: "resize", + options: { + alsoResize: false, + animate: false, + animateDuration: "slow", + animateEasing: "swing", + aspectRatio: false, + autoHide: false, + containment: false, + ghost: false, + grid: false, + handles: "e,s,se", + helper: false, + maxHeight: null, + maxWidth: null, + minHeight: 10, + minWidth: 10, + zIndex: 1000 + }, + _create: function() { + + var self = this, o = this.options; + this.element.addClass("ui-resizable"); + + $.extend(this, { + _aspectRatio: !!(o.aspectRatio), + aspectRatio: o.aspectRatio, + originalElement: this.element, + _proportionallyResizeElements: [], + _helper: o.helper || o.ghost || o.animate ? o.helper || 'ui-resizable-helper' : null + }); + + //Wrap the element if it cannot hold child nodes + if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) { + + //Opera fix for relative positioning + if (/relative/.test(this.element.css('position')) && $.browser.opera) + this.element.css({ position: 'relative', top: 'auto', left: 'auto' }); + + //Create a wrapper element and set the wrapper to the new current internal element + this.element.wrap( + $('
    ').css({ + position: this.element.css('position'), + width: this.element.outerWidth(), + height: this.element.outerHeight(), + top: this.element.css('top'), + left: this.element.css('left') + }) + ); + + //Overwrite the original this.element + this.element = this.element.parent().data( + "resizable", this.element.data('resizable') + ); + + this.elementIsWrapper = true; + + //Move margins to the wrapper + this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") }); + this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0}); + + //Prevent Safari textarea resize + this.originalResizeStyle = this.originalElement.css('resize'); + this.originalElement.css('resize', 'none'); + + //Push the actual element to our proportionallyResize internal array + this._proportionallyResizeElements.push(this.originalElement.css({ position: 'static', zoom: 1, display: 'block' })); + + // avoid IE jump (hard set the margin) + this.originalElement.css({ margin: this.originalElement.css('margin') }); + + // fix handlers offset + this._proportionallyResize(); + + } + + this.handles = o.handles || (!$('.ui-resizable-handle', this.element).length ? "e,s,se" : { n: '.ui-resizable-n', e: '.ui-resizable-e', s: '.ui-resizable-s', w: '.ui-resizable-w', se: '.ui-resizable-se', sw: '.ui-resizable-sw', ne: '.ui-resizable-ne', nw: '.ui-resizable-nw' }); + if(this.handles.constructor == String) { + + if(this.handles == 'all') this.handles = 'n,e,s,w,se,sw,ne,nw'; + var n = this.handles.split(","); this.handles = {}; + + for(var i = 0; i < n.length; i++) { + + var handle = $.trim(n[i]), hname = 'ui-resizable-'+handle; + var axis = $('
    '); + + // increase zIndex of sw, se, ne, nw axis + //TODO : this modifies original option + if(/sw|se|ne|nw/.test(handle)) axis.css({ zIndex: ++o.zIndex }); + + //TODO : What's going on here? + if ('se' == handle) { + axis.addClass('ui-icon ui-icon-gripsmall-diagonal-se'); + }; + + //Insert into internal handles object and append to element + this.handles[handle] = '.ui-resizable-'+handle; + this.element.append(axis); + } + + } + + this._renderAxis = function(target) { + + target = target || this.element; + + for(var i in this.handles) { + + if(this.handles[i].constructor == String) + this.handles[i] = $(this.handles[i], this.element).show(); + + //Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls) + if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) { + + var axis = $(this.handles[i], this.element), padWrapper = 0; + + //Checking the correct pad and border + padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth(); + + //The padding type i have to apply... + var padPos = [ 'padding', + /ne|nw|n/.test(i) ? 'Top' : + /se|sw|s/.test(i) ? 'Bottom' : + /^e$/.test(i) ? 'Right' : 'Left' ].join(""); + + target.css(padPos, padWrapper); + + this._proportionallyResize(); + + } + + //TODO: What's that good for? There's not anything to be executed left + if(!$(this.handles[i]).length) + continue; + + } + }; + + //TODO: make renderAxis a prototype function + this._renderAxis(this.element); + + this._handles = $('.ui-resizable-handle', this.element) + .disableSelection(); + + //Matching axis name + this._handles.mouseover(function() { + if (!self.resizing) { + if (this.className) + var axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i); + //Axis, default = se + self.axis = axis && axis[1] ? axis[1] : 'se'; + } + }); + + //If we want to auto hide the elements + if (o.autoHide) { + this._handles.hide(); + $(this.element) + .addClass("ui-resizable-autohide") + .hover(function() { + $(this).removeClass("ui-resizable-autohide"); + self._handles.show(); + }, + function(){ + if (!self.resizing) { + $(this).addClass("ui-resizable-autohide"); + self._handles.hide(); + } + }); + } + + //Initialize the mouse interaction + this._mouseInit(); + + }, + + destroy: function() { + + this._mouseDestroy(); + + var _destroy = function(exp) { + $(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing") + .removeData("resizable").unbind(".resizable").find('.ui-resizable-handle').remove(); + }; + + //TODO: Unwrap at same DOM position + if (this.elementIsWrapper) { + _destroy(this.element); + var wrapper = this.element; + wrapper.after( + this.originalElement.css({ + position: wrapper.css('position'), + width: wrapper.outerWidth(), + height: wrapper.outerHeight(), + top: wrapper.css('top'), + left: wrapper.css('left') + }) + ).remove(); + } + + this.originalElement.css('resize', this.originalResizeStyle); + _destroy(this.originalElement); + + return this; + }, + + _mouseCapture: function(event) { + var handle = false; + for (var i in this.handles) { + if ($(this.handles[i])[0] == event.target) { + handle = true; + } + } + + return !this.options.disabled && handle; + }, + + _mouseStart: function(event) { + + var o = this.options, iniPos = this.element.position(), el = this.element; + + this.resizing = true; + this.documentScroll = { top: $(document).scrollTop(), left: $(document).scrollLeft() }; + + // bugfix for http://dev.jquery.com/ticket/1749 + if (el.is('.ui-draggable') || (/absolute/).test(el.css('position'))) { + el.css({ position: 'absolute', top: iniPos.top, left: iniPos.left }); + } + + //Opera fixing relative position + if ($.browser.opera && (/relative/).test(el.css('position'))) + el.css({ position: 'relative', top: 'auto', left: 'auto' }); + + this._renderProxy(); + + var curleft = num(this.helper.css('left')), curtop = num(this.helper.css('top')); + + if (o.containment) { + curleft += $(o.containment).scrollLeft() || 0; + curtop += $(o.containment).scrollTop() || 0; + } + + //Store needed variables + this.offset = this.helper.offset(); + this.position = { left: curleft, top: curtop }; + this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() }; + this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() }; + this.originalPosition = { left: curleft, top: curtop }; + this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() }; + this.originalMousePosition = { left: event.pageX, top: event.pageY }; + + //Aspect Ratio + this.aspectRatio = (typeof o.aspectRatio == 'number') ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1); + + var cursor = $('.ui-resizable-' + this.axis).css('cursor'); + $('body').css('cursor', cursor == 'auto' ? this.axis + '-resize' : cursor); + + el.addClass("ui-resizable-resizing"); + this._propagate("start", event); + return true; + }, + + _mouseDrag: function(event) { + + //Increase performance, avoid regex + var el = this.helper, o = this.options, props = {}, + self = this, smp = this.originalMousePosition, a = this.axis; + + var dx = (event.pageX-smp.left)||0, dy = (event.pageY-smp.top)||0; + var trigger = this._change[a]; + if (!trigger) return false; + + // Calculate the attrs that will be change + var data = trigger.apply(this, [event, dx, dy]), ie6 = $.browser.msie && $.browser.version < 7, csdif = this.sizeDiff; + + if (this._aspectRatio || event.shiftKey) + data = this._updateRatio(data, event); + + data = this._respectSize(data, event); + + // plugins callbacks need to be called first + this._propagate("resize", event); + + el.css({ + top: this.position.top + "px", left: this.position.left + "px", + width: this.size.width + "px", height: this.size.height + "px" + }); + + if (!this._helper && this._proportionallyResizeElements.length) + this._proportionallyResize(); + + this._updateCache(data); + + // calling the user callback at the end + this._trigger('resize', event, this.ui()); + + return false; + }, + + _mouseStop: function(event) { + + this.resizing = false; + var o = this.options, self = this; + + if(this._helper) { + var pr = this._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName), + soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : self.sizeDiff.height, + soffsetw = ista ? 0 : self.sizeDiff.width; + + var s = { width: (self.helper.width() - soffsetw), height: (self.helper.height() - soffseth) }, + left = (parseInt(self.element.css('left'), 10) + (self.position.left - self.originalPosition.left)) || null, + top = (parseInt(self.element.css('top'), 10) + (self.position.top - self.originalPosition.top)) || null; + + if (!o.animate) + this.element.css($.extend(s, { top: top, left: left })); + + self.helper.height(self.size.height); + self.helper.width(self.size.width); + + if (this._helper && !o.animate) this._proportionallyResize(); + } + + $('body').css('cursor', 'auto'); + + this.element.removeClass("ui-resizable-resizing"); + + this._propagate("stop", event); + + if (this._helper) this.helper.remove(); + return false; + + }, + + _updateCache: function(data) { + var o = this.options; + this.offset = this.helper.offset(); + if (isNumber(data.left)) this.position.left = data.left; + if (isNumber(data.top)) this.position.top = data.top; + if (isNumber(data.height)) this.size.height = data.height; + if (isNumber(data.width)) this.size.width = data.width; + }, + + _updateRatio: function(data, event) { + + var o = this.options, cpos = this.position, csize = this.size, a = this.axis; + + if (data.height) data.width = (csize.height * this.aspectRatio); + else if (data.width) data.height = (csize.width / this.aspectRatio); + + if (a == 'sw') { + data.left = cpos.left + (csize.width - data.width); + data.top = null; + } + if (a == 'nw') { + data.top = cpos.top + (csize.height - data.height); + data.left = cpos.left + (csize.width - data.width); + } + + return data; + }, + + _respectSize: function(data, event) { + + var el = this.helper, o = this.options, pRatio = this._aspectRatio || event.shiftKey, a = this.axis, + ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height), + isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height); + + if (isminw) data.width = o.minWidth; + if (isminh) data.height = o.minHeight; + if (ismaxw) data.width = o.maxWidth; + if (ismaxh) data.height = o.maxHeight; + + var dw = this.originalPosition.left + this.originalSize.width, dh = this.position.top + this.size.height; + var cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a); + + if (isminw && cw) data.left = dw - o.minWidth; + if (ismaxw && cw) data.left = dw - o.maxWidth; + if (isminh && ch) data.top = dh - o.minHeight; + if (ismaxh && ch) data.top = dh - o.maxHeight; + + // fixing jump error on top/left - bug #2330 + var isNotwh = !data.width && !data.height; + if (isNotwh && !data.left && data.top) data.top = null; + else if (isNotwh && !data.top && data.left) data.left = null; + + return data; + }, + + _proportionallyResize: function() { + + var o = this.options; + if (!this._proportionallyResizeElements.length) return; + var element = this.helper || this.element; + + for (var i=0; i < this._proportionallyResizeElements.length; i++) { + + var prel = this._proportionallyResizeElements[i]; + + if (!this.borderDif) { + var b = [prel.css('borderTopWidth'), prel.css('borderRightWidth'), prel.css('borderBottomWidth'), prel.css('borderLeftWidth')], + p = [prel.css('paddingTop'), prel.css('paddingRight'), prel.css('paddingBottom'), prel.css('paddingLeft')]; + + this.borderDif = $.map(b, function(v, i) { + var border = parseInt(v,10)||0, padding = parseInt(p[i],10)||0; + return border + padding; + }); + } + + if ($.browser.msie && !(!($(element).is(':hidden') || $(element).parents(':hidden').length))) + continue; + + prel.css({ + height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0, + width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0 + }); + + }; + + }, + + _renderProxy: function() { + + var el = this.element, o = this.options; + this.elementOffset = el.offset(); + + if(this._helper) { + + this.helper = this.helper || $('
    '); + + // fix ie6 offset TODO: This seems broken + var ie6 = $.browser.msie && $.browser.version < 7, ie6offset = (ie6 ? 1 : 0), + pxyoffset = ( ie6 ? 2 : -1 ); + + this.helper.addClass(this._helper).css({ + width: this.element.outerWidth() + pxyoffset, + height: this.element.outerHeight() + pxyoffset, + position: 'absolute', + left: this.elementOffset.left - ie6offset +'px', + top: this.elementOffset.top - ie6offset +'px', + zIndex: ++o.zIndex //TODO: Don't modify option + }); + + this.helper + .appendTo("body") + .disableSelection(); + + } else { + this.helper = this.element; + } + + }, + + _change: { + e: function(event, dx, dy) { + return { width: this.originalSize.width + dx }; + }, + w: function(event, dx, dy) { + var o = this.options, cs = this.originalSize, sp = this.originalPosition; + return { left: sp.left + dx, width: cs.width - dx }; + }, + n: function(event, dx, dy) { + var o = this.options, cs = this.originalSize, sp = this.originalPosition; + return { top: sp.top + dy, height: cs.height - dy }; + }, + s: function(event, dx, dy) { + return { height: this.originalSize.height + dy }; + }, + se: function(event, dx, dy) { + return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy])); + }, + sw: function(event, dx, dy) { + return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy])); + }, + ne: function(event, dx, dy) { + return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy])); + }, + nw: function(event, dx, dy) { + return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy])); + } + }, + + _propagate: function(n, event) { + $.ui.plugin.call(this, n, [event, this.ui()]); + (n != "resize" && this._trigger(n, event, this.ui())); + }, + + plugins: {}, + + ui: function() { + return { + originalElement: this.originalElement, + element: this.element, + helper: this.helper, + position: this.position, + size: this.size, + originalSize: this.originalSize, + originalPosition: this.originalPosition + }; + } + +}); + +$.extend($.ui.resizable, { + version: "1.8.11" +}); + +/* + * Resizable Extensions + */ + +$.ui.plugin.add("resizable", "alsoResize", { + + start: function (event, ui) { + var self = $(this).data("resizable"), o = self.options; + + var _store = function (exp) { + $(exp).each(function() { + var el = $(this); + el.data("resizable-alsoresize", { + width: parseInt(el.width(), 10), height: parseInt(el.height(), 10), + left: parseInt(el.css('left'), 10), top: parseInt(el.css('top'), 10), + position: el.css('position') // to reset Opera on stop() + }); + }); + }; + + if (typeof(o.alsoResize) == 'object' && !o.alsoResize.parentNode) { + if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); } + else { $.each(o.alsoResize, function (exp) { _store(exp); }); } + }else{ + _store(o.alsoResize); + } + }, + + resize: function (event, ui) { + var self = $(this).data("resizable"), o = self.options, os = self.originalSize, op = self.originalPosition; + + var delta = { + height: (self.size.height - os.height) || 0, width: (self.size.width - os.width) || 0, + top: (self.position.top - op.top) || 0, left: (self.position.left - op.left) || 0 + }, + + _alsoResize = function (exp, c) { + $(exp).each(function() { + var el = $(this), start = $(this).data("resizable-alsoresize"), style = {}, + css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ['width', 'height'] : ['width', 'height', 'top', 'left']; + + $.each(css, function (i, prop) { + var sum = (start[prop]||0) + (delta[prop]||0); + if (sum && sum >= 0) + style[prop] = sum || null; + }); + + // Opera fixing relative position + if ($.browser.opera && /relative/.test(el.css('position'))) { + self._revertToRelativePosition = true; + el.css({ position: 'absolute', top: 'auto', left: 'auto' }); + } + + el.css(style); + }); + }; + + if (typeof(o.alsoResize) == 'object' && !o.alsoResize.nodeType) { + $.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); }); + }else{ + _alsoResize(o.alsoResize); + } + }, + + stop: function (event, ui) { + var self = $(this).data("resizable"), o = self.options; + + var _reset = function (exp) { + $(exp).each(function() { + var el = $(this); + // reset position for Opera - no need to verify it was changed + el.css({ position: el.data("resizable-alsoresize").position }); + }); + }; + + if (self._revertToRelativePosition) { + self._revertToRelativePosition = false; + if (typeof(o.alsoResize) == 'object' && !o.alsoResize.nodeType) { + $.each(o.alsoResize, function (exp) { _reset(exp); }); + }else{ + _reset(o.alsoResize); + } + } + + $(this).removeData("resizable-alsoresize"); + } +}); + +$.ui.plugin.add("resizable", "animate", { + + stop: function(event, ui) { + var self = $(this).data("resizable"), o = self.options; + + var pr = self._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName), + soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : self.sizeDiff.height, + soffsetw = ista ? 0 : self.sizeDiff.width; + + var style = { width: (self.size.width - soffsetw), height: (self.size.height - soffseth) }, + left = (parseInt(self.element.css('left'), 10) + (self.position.left - self.originalPosition.left)) || null, + top = (parseInt(self.element.css('top'), 10) + (self.position.top - self.originalPosition.top)) || null; + + self.element.animate( + $.extend(style, top && left ? { top: top, left: left } : {}), { + duration: o.animateDuration, + easing: o.animateEasing, + step: function() { + + var data = { + width: parseInt(self.element.css('width'), 10), + height: parseInt(self.element.css('height'), 10), + top: parseInt(self.element.css('top'), 10), + left: parseInt(self.element.css('left'), 10) + }; + + if (pr && pr.length) $(pr[0]).css({ width: data.width, height: data.height }); + + // propagating resize, and updating values for each animation step + self._updateCache(data); + self._propagate("resize", event); + + } + } + ); + } + +}); + +$.ui.plugin.add("resizable", "containment", { + + start: function(event, ui) { + var self = $(this).data("resizable"), o = self.options, el = self.element; + var oc = o.containment, ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc; + if (!ce) return; + + self.containerElement = $(ce); + + if (/document/.test(oc) || oc == document) { + self.containerOffset = { left: 0, top: 0 }; + self.containerPosition = { left: 0, top: 0 }; + + self.parentData = { + element: $(document), left: 0, top: 0, + width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight + }; + } + + // i'm a node, so compute top, left, right, bottom + else { + var element = $(ce), p = []; + $([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); }); + + self.containerOffset = element.offset(); + self.containerPosition = element.position(); + self.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) }; + + var co = self.containerOffset, ch = self.containerSize.height, cw = self.containerSize.width, + width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ), height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch); + + self.parentData = { + element: ce, left: co.left, top: co.top, width: width, height: height + }; + } + }, + + resize: function(event, ui) { + var self = $(this).data("resizable"), o = self.options, + ps = self.containerSize, co = self.containerOffset, cs = self.size, cp = self.position, + pRatio = self._aspectRatio || event.shiftKey, cop = { top:0, left:0 }, ce = self.containerElement; + + if (ce[0] != document && (/static/).test(ce.css('position'))) cop = co; + + if (cp.left < (self._helper ? co.left : 0)) { + self.size.width = self.size.width + (self._helper ? (self.position.left - co.left) : (self.position.left - cop.left)); + if (pRatio) self.size.height = self.size.width / o.aspectRatio; + self.position.left = o.helper ? co.left : 0; + } + + if (cp.top < (self._helper ? co.top : 0)) { + self.size.height = self.size.height + (self._helper ? (self.position.top - co.top) : self.position.top); + if (pRatio) self.size.width = self.size.height * o.aspectRatio; + self.position.top = self._helper ? co.top : 0; + } + + self.offset.left = self.parentData.left+self.position.left; + self.offset.top = self.parentData.top+self.position.top; + + var woset = Math.abs( (self._helper ? self.offset.left - cop.left : (self.offset.left - cop.left)) + self.sizeDiff.width ), + hoset = Math.abs( (self._helper ? self.offset.top - cop.top : (self.offset.top - co.top)) + self.sizeDiff.height ); + + var isParent = self.containerElement.get(0) == self.element.parent().get(0), + isOffsetRelative = /relative|absolute/.test(self.containerElement.css('position')); + + if(isParent && isOffsetRelative) woset -= self.parentData.left; + + if (woset + self.size.width >= self.parentData.width) { + self.size.width = self.parentData.width - woset; + if (pRatio) self.size.height = self.size.width / self.aspectRatio; + } + + if (hoset + self.size.height >= self.parentData.height) { + self.size.height = self.parentData.height - hoset; + if (pRatio) self.size.width = self.size.height * self.aspectRatio; + } + }, + + stop: function(event, ui){ + var self = $(this).data("resizable"), o = self.options, cp = self.position, + co = self.containerOffset, cop = self.containerPosition, ce = self.containerElement; + + var helper = $(self.helper), ho = helper.offset(), w = helper.outerWidth() - self.sizeDiff.width, h = helper.outerHeight() - self.sizeDiff.height; + + if (self._helper && !o.animate && (/relative/).test(ce.css('position'))) + $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h }); + + if (self._helper && !o.animate && (/static/).test(ce.css('position'))) + $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h }); + + } +}); + +$.ui.plugin.add("resizable", "ghost", { + + start: function(event, ui) { + + var self = $(this).data("resizable"), o = self.options, cs = self.size; + + self.ghost = self.originalElement.clone(); + self.ghost + .css({ opacity: .25, display: 'block', position: 'relative', height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 }) + .addClass('ui-resizable-ghost') + .addClass(typeof o.ghost == 'string' ? o.ghost : ''); + + self.ghost.appendTo(self.helper); + + }, + + resize: function(event, ui){ + var self = $(this).data("resizable"), o = self.options; + if (self.ghost) self.ghost.css({ position: 'relative', height: self.size.height, width: self.size.width }); + }, + + stop: function(event, ui){ + var self = $(this).data("resizable"), o = self.options; + if (self.ghost && self.helper) self.helper.get(0).removeChild(self.ghost.get(0)); + } + +}); + +$.ui.plugin.add("resizable", "grid", { + + resize: function(event, ui) { + var self = $(this).data("resizable"), o = self.options, cs = self.size, os = self.originalSize, op = self.originalPosition, a = self.axis, ratio = o._aspectRatio || event.shiftKey; + o.grid = typeof o.grid == "number" ? [o.grid, o.grid] : o.grid; + var ox = Math.round((cs.width - os.width) / (o.grid[0]||1)) * (o.grid[0]||1), oy = Math.round((cs.height - os.height) / (o.grid[1]||1)) * (o.grid[1]||1); + + if (/^(se|s|e)$/.test(a)) { + self.size.width = os.width + ox; + self.size.height = os.height + oy; + } + else if (/^(ne)$/.test(a)) { + self.size.width = os.width + ox; + self.size.height = os.height + oy; + self.position.top = op.top - oy; + } + else if (/^(sw)$/.test(a)) { + self.size.width = os.width + ox; + self.size.height = os.height + oy; + self.position.left = op.left - ox; + } + else { + self.size.width = os.width + ox; + self.size.height = os.height + oy; + self.position.top = op.top - oy; + self.position.left = op.left - ox; + } + } + +}); + +var num = function(v) { + return parseInt(v, 10) || 0; +}; + +var isNumber = function(value) { + return !isNaN(parseInt(value, 10)); +}; + +})(jQuery); +/* + * jQuery UI Selectable 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +$.widget("ui.selectable", $.ui.mouse, { + options: { + appendTo: 'body', + autoRefresh: true, + distance: 0, + filter: '*', + tolerance: 'touch' + }, + _create: function() { + var self = this; + + this.element.addClass("ui-selectable"); + + this.dragged = false; + + // cache selectee children based on filter + var selectees; + this.refresh = function() { + selectees = $(self.options.filter, self.element[0]); + selectees.each(function() { + var $this = $(this); + var pos = $this.offset(); + $.data(this, "selectable-item", { + element: this, + $element: $this, + left: pos.left, + top: pos.top, + right: pos.left + $this.outerWidth(), + bottom: pos.top + $this.outerHeight(), + startselected: false, + selected: $this.hasClass('ui-selected'), + selecting: $this.hasClass('ui-selecting'), + unselecting: $this.hasClass('ui-unselecting') + }); + }); + }; + this.refresh(); + + this.selectees = selectees.addClass("ui-selectee"); + + this._mouseInit(); + + this.helper = $("
    "); + }, + + destroy: function() { + this.selectees + .removeClass("ui-selectee") + .removeData("selectable-item"); + this.element + .removeClass("ui-selectable ui-selectable-disabled") + .removeData("selectable") + .unbind(".selectable"); + this._mouseDestroy(); + + return this; + }, + + _mouseStart: function(event) { + var self = this; + + this.opos = [event.pageX, event.pageY]; + + if (this.options.disabled) + return; + + var options = this.options; + + this.selectees = $(options.filter, this.element[0]); + + this._trigger("start", event); + + $(options.appendTo).append(this.helper); + // position helper (lasso) + this.helper.css({ + "left": event.clientX, + "top": event.clientY, + "width": 0, + "height": 0 + }); + + if (options.autoRefresh) { + this.refresh(); + } + + this.selectees.filter('.ui-selected').each(function() { + var selectee = $.data(this, "selectable-item"); + selectee.startselected = true; + if (!event.metaKey) { + selectee.$element.removeClass('ui-selected'); + selectee.selected = false; + selectee.$element.addClass('ui-unselecting'); + selectee.unselecting = true; + // selectable UNSELECTING callback + self._trigger("unselecting", event, { + unselecting: selectee.element + }); + } + }); + + $(event.target).parents().andSelf().each(function() { + var selectee = $.data(this, "selectable-item"); + if (selectee) { + var doSelect = !event.metaKey || !selectee.$element.hasClass('ui-selected'); + selectee.$element + .removeClass(doSelect ? "ui-unselecting" : "ui-selected") + .addClass(doSelect ? "ui-selecting" : "ui-unselecting"); + selectee.unselecting = !doSelect; + selectee.selecting = doSelect; + selectee.selected = doSelect; + // selectable (UN)SELECTING callback + if (doSelect) { + self._trigger("selecting", event, { + selecting: selectee.element + }); + } else { + self._trigger("unselecting", event, { + unselecting: selectee.element + }); + } + return false; + } + }); + + }, + + _mouseDrag: function(event) { + var self = this; + this.dragged = true; + + if (this.options.disabled) + return; + + var options = this.options; + + var x1 = this.opos[0], y1 = this.opos[1], x2 = event.pageX, y2 = event.pageY; + if (x1 > x2) { var tmp = x2; x2 = x1; x1 = tmp; } + if (y1 > y2) { var tmp = y2; y2 = y1; y1 = tmp; } + this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1}); + + this.selectees.each(function() { + var selectee = $.data(this, "selectable-item"); + //prevent helper from being selected if appendTo: selectable + if (!selectee || selectee.element == self.element[0]) + return; + var hit = false; + if (options.tolerance == 'touch') { + hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) ); + } else if (options.tolerance == 'fit') { + hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2); + } + + if (hit) { + // SELECT + if (selectee.selected) { + selectee.$element.removeClass('ui-selected'); + selectee.selected = false; + } + if (selectee.unselecting) { + selectee.$element.removeClass('ui-unselecting'); + selectee.unselecting = false; + } + if (!selectee.selecting) { + selectee.$element.addClass('ui-selecting'); + selectee.selecting = true; + // selectable SELECTING callback + self._trigger("selecting", event, { + selecting: selectee.element + }); + } + } else { + // UNSELECT + if (selectee.selecting) { + if (event.metaKey && selectee.startselected) { + selectee.$element.removeClass('ui-selecting'); + selectee.selecting = false; + selectee.$element.addClass('ui-selected'); + selectee.selected = true; + } else { + selectee.$element.removeClass('ui-selecting'); + selectee.selecting = false; + if (selectee.startselected) { + selectee.$element.addClass('ui-unselecting'); + selectee.unselecting = true; + } + // selectable UNSELECTING callback + self._trigger("unselecting", event, { + unselecting: selectee.element + }); + } + } + if (selectee.selected) { + if (!event.metaKey && !selectee.startselected) { + selectee.$element.removeClass('ui-selected'); + selectee.selected = false; + + selectee.$element.addClass('ui-unselecting'); + selectee.unselecting = true; + // selectable UNSELECTING callback + self._trigger("unselecting", event, { + unselecting: selectee.element + }); + } + } + } + }); + + return false; + }, + + _mouseStop: function(event) { + var self = this; + + this.dragged = false; + + var options = this.options; + + $('.ui-unselecting', this.element[0]).each(function() { + var selectee = $.data(this, "selectable-item"); + selectee.$element.removeClass('ui-unselecting'); + selectee.unselecting = false; + selectee.startselected = false; + self._trigger("unselected", event, { + unselected: selectee.element + }); + }); + $('.ui-selecting', this.element[0]).each(function() { + var selectee = $.data(this, "selectable-item"); + selectee.$element.removeClass('ui-selecting').addClass('ui-selected'); + selectee.selecting = false; + selectee.selected = true; + selectee.startselected = true; + self._trigger("selected", event, { + selected: selectee.element + }); + }); + this._trigger("stop", event); + + this.helper.remove(); + + return false; + } + +}); + +$.extend($.ui.selectable, { + version: "1.8.11" +}); + +})(jQuery); +/* + * jQuery UI Sortable 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Sortables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +$.widget("ui.sortable", $.ui.mouse, { + widgetEventPrefix: "sort", + options: { + appendTo: "parent", + axis: false, + connectWith: false, + containment: false, + cursor: 'auto', + cursorAt: false, + dropOnEmpty: true, + forcePlaceholderSize: false, + forceHelperSize: false, + grid: false, + handle: false, + helper: "original", + items: '> *', + opacity: false, + placeholder: false, + revert: false, + scroll: true, + scrollSensitivity: 20, + scrollSpeed: 20, + scope: "default", + tolerance: "intersect", + zIndex: 1000 + }, + _create: function() { + + var o = this.options; + this.containerCache = {}; + this.element.addClass("ui-sortable"); + + //Get the items + this.refresh(); + + //Let's determine if the items are being displayed horizontally + this.floating = this.items.length ? (/left|right/).test(this.items[0].item.css('float')) || (/inline|table-cell/).test(this.items[0].item.css('display')) : false; + + //Let's determine the parent's offset + this.offset = this.element.offset(); + + //Initialize mouse events for interaction + this._mouseInit(); + + }, + + destroy: function() { + this.element + .removeClass("ui-sortable ui-sortable-disabled") + .removeData("sortable") + .unbind(".sortable"); + this._mouseDestroy(); + + for ( var i = this.items.length - 1; i >= 0; i-- ) + this.items[i].item.removeData("sortable-item"); + + return this; + }, + + _setOption: function(key, value){ + if ( key === "disabled" ) { + this.options[ key ] = value; + + this.widget() + [ value ? "addClass" : "removeClass"]( "ui-sortable-disabled" ); + } else { + // Don't call widget base _setOption for disable as it adds ui-state-disabled class + $.Widget.prototype._setOption.apply(this, arguments); + } + }, + + _mouseCapture: function(event, overrideHandle) { + + if (this.reverting) { + return false; + } + + if(this.options.disabled || this.options.type == 'static') return false; + + //We have to refresh the items data once first + this._refreshItems(event); + + //Find out if the clicked node (or one of its parents) is a actual item in this.items + var currentItem = null, self = this, nodes = $(event.target).parents().each(function() { + if($.data(this, 'sortable-item') == self) { + currentItem = $(this); + return false; + } + }); + if($.data(event.target, 'sortable-item') == self) currentItem = $(event.target); + + if(!currentItem) return false; + if(this.options.handle && !overrideHandle) { + var validHandle = false; + + $(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; }); + if(!validHandle) return false; + } + + this.currentItem = currentItem; + this._removeCurrentsFromItems(); + return true; + + }, + + _mouseStart: function(event, overrideHandle, noActivation) { + + var o = this.options, self = this; + this.currentContainer = this; + + //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture + this.refreshPositions(); + + //Create and append the visible helper + this.helper = this._createHelper(event); + + //Cache the helper size + this._cacheHelperProportions(); + + /* + * - Position generation - + * This block generates everything position related - it's the core of draggables. + */ + + //Cache the margins of the original element + this._cacheMargins(); + + //Get the next scrolling parent + this.scrollParent = this.helper.scrollParent(); + + //The element's absolute position on the page minus margins + this.offset = this.currentItem.offset(); + this.offset = { + top: this.offset.top - this.margins.top, + left: this.offset.left - this.margins.left + }; + + // Only after we got the offset, we can change the helper's position to absolute + // TODO: Still need to figure out a way to make relative sorting possible + this.helper.css("position", "absolute"); + this.cssPosition = this.helper.css("position"); + + $.extend(this.offset, { + click: { //Where the click happened, relative to the element + left: event.pageX - this.offset.left, + top: event.pageY - this.offset.top + }, + parent: this._getParentOffset(), + relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper + }); + + //Generate the original position + this.originalPosition = this._generatePosition(event); + this.originalPageX = event.pageX; + this.originalPageY = event.pageY; + + //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied + (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); + + //Cache the former DOM position + this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] }; + + //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way + if(this.helper[0] != this.currentItem[0]) { + this.currentItem.hide(); + } + + //Create the placeholder + this._createPlaceholder(); + + //Set a containment if given in the options + if(o.containment) + this._setContainment(); + + if(o.cursor) { // cursor option + if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor"); + $('body').css("cursor", o.cursor); + } + + if(o.opacity) { // opacity option + if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity"); + this.helper.css("opacity", o.opacity); + } + + if(o.zIndex) { // zIndex option + if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex"); + this.helper.css("zIndex", o.zIndex); + } + + //Prepare scrolling + if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') + this.overflowOffset = this.scrollParent.offset(); + + //Call callbacks + this._trigger("start", event, this._uiHash()); + + //Recache the helper size + if(!this._preserveHelperProportions) + this._cacheHelperProportions(); + + + //Post 'activate' events to possible containers + if(!noActivation) { + for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, self._uiHash(this)); } + } + + //Prepare possible droppables + if($.ui.ddmanager) + $.ui.ddmanager.current = this; + + if ($.ui.ddmanager && !o.dropBehaviour) + $.ui.ddmanager.prepareOffsets(this, event); + + this.dragging = true; + + this.helper.addClass("ui-sortable-helper"); + this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position + return true; + + }, + + _mouseDrag: function(event) { + + //Compute the helpers position + this.position = this._generatePosition(event); + this.positionAbs = this._convertPositionTo("absolute"); + + if (!this.lastPositionAbs) { + this.lastPositionAbs = this.positionAbs; + } + + //Do scrolling + if(this.options.scroll) { + var o = this.options, scrolled = false; + if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') { + + if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; + else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; + + if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; + else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; + + } else { + + if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) + scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); + else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) + scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); + + if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) + scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); + else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) + scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); + + } + + if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) + $.ui.ddmanager.prepareOffsets(this, event); + } + + //Regenerate the absolute position used for position checks + this.positionAbs = this._convertPositionTo("absolute"); + + //Set the helper position + if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px'; + if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px'; + + //Rearrange + for (var i = this.items.length - 1; i >= 0; i--) { + + //Cache variables and intersection, continue if no intersection + var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item); + if (!intersection) continue; + + if(itemElement != this.currentItem[0] //cannot intersect with itself + && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before + && !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked + && (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true) + //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container + ) { + + this.direction = intersection == 1 ? "down" : "up"; + + if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) { + this._rearrange(event, item); + } else { + break; + } + + this._trigger("change", event, this._uiHash()); + break; + } + } + + //Post events to containers + this._contactContainers(event); + + //Interconnect with droppables + if($.ui.ddmanager) $.ui.ddmanager.drag(this, event); + + //Call callbacks + this._trigger('sort', event, this._uiHash()); + + this.lastPositionAbs = this.positionAbs; + return false; + + }, + + _mouseStop: function(event, noPropagation) { + + if(!event) return; + + //If we are using droppables, inform the manager about the drop + if ($.ui.ddmanager && !this.options.dropBehaviour) + $.ui.ddmanager.drop(this, event); + + if(this.options.revert) { + var self = this; + var cur = self.placeholder.offset(); + + self.reverting = true; + + $(this.helper).animate({ + left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft), + top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop) + }, parseInt(this.options.revert, 10) || 500, function() { + self._clear(event); + }); + } else { + this._clear(event, noPropagation); + } + + return false; + + }, + + cancel: function() { + + var self = this; + + if(this.dragging) { + + this._mouseUp({ target: null }); + + if(this.options.helper == "original") + this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); + else + this.currentItem.show(); + + //Post deactivating events to containers + for (var i = this.containers.length - 1; i >= 0; i--){ + this.containers[i]._trigger("deactivate", null, self._uiHash(this)); + if(this.containers[i].containerCache.over) { + this.containers[i]._trigger("out", null, self._uiHash(this)); + this.containers[i].containerCache.over = 0; + } + } + + } + + if (this.placeholder) { + //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! + if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]); + if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove(); + + $.extend(this, { + helper: null, + dragging: false, + reverting: false, + _noFinalSort: null + }); + + if(this.domPosition.prev) { + $(this.domPosition.prev).after(this.currentItem); + } else { + $(this.domPosition.parent).prepend(this.currentItem); + } + } + + return this; + + }, + + serialize: function(o) { + + var items = this._getItemsAsjQuery(o && o.connected); + var str = []; o = o || {}; + + $(items).each(function() { + var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/)); + if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2])); + }); + + if(!str.length && o.key) { + str.push(o.key + '='); + } + + return str.join('&'); + + }, + + toArray: function(o) { + + var items = this._getItemsAsjQuery(o && o.connected); + var ret = []; o = o || {}; + + items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); }); + return ret; + + }, + + /* Be careful with the following core functions */ + _intersectsWith: function(item) { + + var x1 = this.positionAbs.left, + x2 = x1 + this.helperProportions.width, + y1 = this.positionAbs.top, + y2 = y1 + this.helperProportions.height; + + var l = item.left, + r = l + item.width, + t = item.top, + b = t + item.height; + + var dyClick = this.offset.click.top, + dxClick = this.offset.click.left; + + var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r; + + if( this.options.tolerance == "pointer" + || this.options.forcePointerForContainers + || (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height']) + ) { + return isOverElement; + } else { + + return (l < x1 + (this.helperProportions.width / 2) // Right Half + && x2 - (this.helperProportions.width / 2) < r // Left Half + && t < y1 + (this.helperProportions.height / 2) // Bottom Half + && y2 - (this.helperProportions.height / 2) < b ); // Top Half + + } + }, + + _intersectsWithPointer: function(item) { + + var isOverElementHeight = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), + isOverElementWidth = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), + isOverElement = isOverElementHeight && isOverElementWidth, + verticalDirection = this._getDragVerticalDirection(), + horizontalDirection = this._getDragHorizontalDirection(); + + if (!isOverElement) + return false; + + return this.floating ? + ( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 ) + : ( verticalDirection && (verticalDirection == "down" ? 2 : 1) ); + + }, + + _intersectsWithSides: function(item) { + + var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height), + isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width), + verticalDirection = this._getDragVerticalDirection(), + horizontalDirection = this._getDragHorizontalDirection(); + + if (this.floating && horizontalDirection) { + return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf)); + } else { + return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf)); + } + + }, + + _getDragVerticalDirection: function() { + var delta = this.positionAbs.top - this.lastPositionAbs.top; + return delta != 0 && (delta > 0 ? "down" : "up"); + }, + + _getDragHorizontalDirection: function() { + var delta = this.positionAbs.left - this.lastPositionAbs.left; + return delta != 0 && (delta > 0 ? "right" : "left"); + }, + + refresh: function(event) { + this._refreshItems(event); + this.refreshPositions(); + return this; + }, + + _connectWith: function() { + var options = this.options; + return options.connectWith.constructor == String + ? [options.connectWith] + : options.connectWith; + }, + + _getItemsAsjQuery: function(connected) { + + var self = this; + var items = []; + var queries = []; + var connectWith = this._connectWith(); + + if(connectWith && connected) { + for (var i = connectWith.length - 1; i >= 0; i--){ + var cur = $(connectWith[i]); + for (var j = cur.length - 1; j >= 0; j--){ + var inst = $.data(cur[j], 'sortable'); + if(inst && inst != this && !inst.options.disabled) { + queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), inst]); + } + }; + }; + } + + queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), this]); + + for (var i = queries.length - 1; i >= 0; i--){ + queries[i][0].each(function() { + items.push(this); + }); + }; + + return $(items); + + }, + + _removeCurrentsFromItems: function() { + + var list = this.currentItem.find(":data(sortable-item)"); + + for (var i=0; i < this.items.length; i++) { + + for (var j=0; j < list.length; j++) { + if(list[j] == this.items[i].item[0]) + this.items.splice(i,1); + }; + + }; + + }, + + _refreshItems: function(event) { + + this.items = []; + this.containers = [this]; + var items = this.items; + var self = this; + var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]]; + var connectWith = this._connectWith(); + + if(connectWith) { + for (var i = connectWith.length - 1; i >= 0; i--){ + var cur = $(connectWith[i]); + for (var j = cur.length - 1; j >= 0; j--){ + var inst = $.data(cur[j], 'sortable'); + if(inst && inst != this && !inst.options.disabled) { + queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]); + this.containers.push(inst); + } + }; + }; + } + + for (var i = queries.length - 1; i >= 0; i--) { + var targetData = queries[i][1]; + var _queries = queries[i][0]; + + for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) { + var item = $(_queries[j]); + + item.data('sortable-item', targetData); // Data for target checking (mouse manager) + + items.push({ + item: item, + instance: targetData, + width: 0, height: 0, + left: 0, top: 0 + }); + }; + }; + + }, + + refreshPositions: function(fast) { + + //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change + if(this.offsetParent && this.helper) { + this.offset.parent = this._getParentOffset(); + } + + for (var i = this.items.length - 1; i >= 0; i--){ + var item = this.items[i]; + + var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item; + + if (!fast) { + item.width = t.outerWidth(); + item.height = t.outerHeight(); + } + + var p = t.offset(); + item.left = p.left; + item.top = p.top; + }; + + if(this.options.custom && this.options.custom.refreshContainers) { + this.options.custom.refreshContainers.call(this); + } else { + for (var i = this.containers.length - 1; i >= 0; i--){ + var p = this.containers[i].element.offset(); + this.containers[i].containerCache.left = p.left; + this.containers[i].containerCache.top = p.top; + this.containers[i].containerCache.width = this.containers[i].element.outerWidth(); + this.containers[i].containerCache.height = this.containers[i].element.outerHeight(); + }; + } + + return this; + }, + + _createPlaceholder: function(that) { + + var self = that || this, o = self.options; + + if(!o.placeholder || o.placeholder.constructor == String) { + var className = o.placeholder; + o.placeholder = { + element: function() { + + var el = $(document.createElement(self.currentItem[0].nodeName)) + .addClass(className || self.currentItem[0].className+" ui-sortable-placeholder") + .removeClass("ui-sortable-helper")[0]; + + if(!className) + el.style.visibility = "hidden"; + + return el; + }, + update: function(container, p) { + + // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that + // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified + if(className && !o.forcePlaceholderSize) return; + + //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item + if(!p.height()) { p.height(self.currentItem.innerHeight() - parseInt(self.currentItem.css('paddingTop')||0, 10) - parseInt(self.currentItem.css('paddingBottom')||0, 10)); }; + if(!p.width()) { p.width(self.currentItem.innerWidth() - parseInt(self.currentItem.css('paddingLeft')||0, 10) - parseInt(self.currentItem.css('paddingRight')||0, 10)); }; + } + }; + } + + //Create the placeholder + self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem)); + + //Append it after the actual current item + self.currentItem.after(self.placeholder); + + //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) + o.placeholder.update(self, self.placeholder); + + }, + + _contactContainers: function(event) { + + // get innermost container that intersects with item + var innermostContainer = null, innermostIndex = null; + + + for (var i = this.containers.length - 1; i >= 0; i--){ + + // never consider a container that's located within the item itself + if($.ui.contains(this.currentItem[0], this.containers[i].element[0])) + continue; + + if(this._intersectsWith(this.containers[i].containerCache)) { + + // if we've already found a container and it's more "inner" than this, then continue + if(innermostContainer && $.ui.contains(this.containers[i].element[0], innermostContainer.element[0])) + continue; + + innermostContainer = this.containers[i]; + innermostIndex = i; + + } else { + // container doesn't intersect. trigger "out" event if necessary + if(this.containers[i].containerCache.over) { + this.containers[i]._trigger("out", event, this._uiHash(this)); + this.containers[i].containerCache.over = 0; + } + } + + } + + // if no intersecting containers found, return + if(!innermostContainer) return; + + // move the item into the container if it's not there already + if(this.containers.length === 1) { + this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); + this.containers[innermostIndex].containerCache.over = 1; + } else if(this.currentContainer != this.containers[innermostIndex]) { + + //When entering a new container, we will find the item with the least distance and append our item near it + var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[innermostIndex].floating ? 'left' : 'top']; + for (var j = this.items.length - 1; j >= 0; j--) { + if(!$.ui.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue; + var cur = this.items[j][this.containers[innermostIndex].floating ? 'left' : 'top']; + if(Math.abs(cur - base) < dist) { + dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; + } + } + + if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled + return; + + this.currentContainer = this.containers[innermostIndex]; + itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); + this._trigger("change", event, this._uiHash()); + this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); + + //Update the placeholder + this.options.placeholder.update(this.currentContainer, this.placeholder); + + this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); + this.containers[innermostIndex].containerCache.over = 1; + } + + + }, + + _createHelper: function(event) { + + var o = this.options; + var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem); + + if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already + $(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]); + + if(helper[0] == this.currentItem[0]) + this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") }; + + if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width()); + if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height()); + + return helper; + + }, + + _adjustOffsetFromHelper: function(obj) { + if (typeof obj == 'string') { + obj = obj.split(' '); + } + if ($.isArray(obj)) { + obj = {left: +obj[0], top: +obj[1] || 0}; + } + if ('left' in obj) { + this.offset.click.left = obj.left + this.margins.left; + } + if ('right' in obj) { + this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; + } + if ('top' in obj) { + this.offset.click.top = obj.top + this.margins.top; + } + if ('bottom' in obj) { + this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; + } + }, + + _getParentOffset: function() { + + + //Get the offsetParent and cache its position + this.offsetParent = this.helper.offsetParent(); + var po = this.offsetParent.offset(); + + // This is a special case where we need to modify a offset calculated on start, since the following happened: + // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent + // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that + // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag + if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) { + po.left += this.scrollParent.scrollLeft(); + po.top += this.scrollParent.scrollTop(); + } + + if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information + || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix + po = { top: 0, left: 0 }; + + return { + top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), + left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) + }; + + }, + + _getRelativeOffset: function() { + + if(this.cssPosition == "relative") { + var p = this.currentItem.position(); + return { + top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), + left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() + }; + } else { + return { top: 0, left: 0 }; + } + + }, + + _cacheMargins: function() { + this.margins = { + left: (parseInt(this.currentItem.css("marginLeft"),10) || 0), + top: (parseInt(this.currentItem.css("marginTop"),10) || 0) + }; + }, + + _cacheHelperProportions: function() { + this.helperProportions = { + width: this.helper.outerWidth(), + height: this.helper.outerHeight() + }; + }, + + _setContainment: function() { + + var o = this.options; + if(o.containment == 'parent') o.containment = this.helper[0].parentNode; + if(o.containment == 'document' || o.containment == 'window') this.containment = [ + 0 - this.offset.relative.left - this.offset.parent.left, + 0 - this.offset.relative.top - this.offset.parent.top, + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left, + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top + ]; + + if(!(/^(document|window|parent)$/).test(o.containment)) { + var ce = $(o.containment)[0]; + var co = $(o.containment).offset(); + var over = ($(ce).css("overflow") != 'hidden'); + + this.containment = [ + co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left, + co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top, + co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left, + co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top + ]; + } + + }, + + _convertPositionTo: function(d, pos) { + + if(!pos) pos = this.position; + var mod = d == "absolute" ? 1 : -1; + var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); + + return { + top: ( + pos.top // The absolute mouse position + + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent + + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border) + - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) + ), + left: ( + pos.left // The absolute mouse position + + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent + + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border) + - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) + ) + }; + + }, + + _generatePosition: function(event) { + + var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); + + // This is another very weird special case that only happens for relative elements: + // 1. If the css position is relative + // 2. and the scroll parent is the document or similar to the offset parent + // we have to refresh the relative offset during the scroll so there are no jumps + if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) { + this.offset.relative = this._getRelativeOffset(); + } + + var pageX = event.pageX; + var pageY = event.pageY; + + /* + * - Position constraining - + * Constrain the position to a mix of grid, containment. + */ + + if(this.originalPosition) { //If we are not dragging yet, we won't check for options + + if(this.containment) { + if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left; + if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top; + if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left; + if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top; + } + + if(o.grid) { + var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1]; + pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; + + var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0]; + pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; + } + + } + + return { + top: ( + pageY // The absolute mouse position + - this.offset.click.top // Click offset (relative to the element) + - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent + - this.offset.parent.top // The offsetParent's offset without borders (offset + border) + + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) + ), + left: ( + pageX // The absolute mouse position + - this.offset.click.left // Click offset (relative to the element) + - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent + - this.offset.parent.left // The offsetParent's offset without borders (offset + border) + + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) + ) + }; + + }, + + _rearrange: function(event, i, a, hardRefresh) { + + a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling)); + + //Various things done here to improve the performance: + // 1. we create a setTimeout, that calls refreshPositions + // 2. on the instance, we have a counter variable, that get's higher after every append + // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same + // 4. this lets only the last addition to the timeout stack through + this.counter = this.counter ? ++this.counter : 1; + var self = this, counter = this.counter; + + window.setTimeout(function() { + if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove + },0); + + }, + + _clear: function(event, noPropagation) { + + this.reverting = false; + // We delay all events that have to be triggered to after the point where the placeholder has been removed and + // everything else normalized again + var delayedTriggers = [], self = this; + + // We first have to update the dom position of the actual currentItem + // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088) + if(!this._noFinalSort && this.currentItem[0].parentNode) this.placeholder.before(this.currentItem); + this._noFinalSort = null; + + if(this.helper[0] == this.currentItem[0]) { + for(var i in this._storedCSS) { + if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = ''; + } + this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); + } else { + this.currentItem.show(); + } + + if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); }); + if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed + if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element + if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); + for (var i = this.containers.length - 1; i >= 0; i--){ + if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) { + delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.containers[i])); + delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.containers[i])); + } + }; + }; + + //Post events to containers + for (var i = this.containers.length - 1; i >= 0; i--){ + if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i])); + if(this.containers[i].containerCache.over) { + delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i])); + this.containers[i].containerCache.over = 0; + } + } + + //Do what was originally in plugins + if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor + if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity + if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index + + this.dragging = false; + if(this.cancelHelperRemoval) { + if(!noPropagation) { + this._trigger("beforeStop", event, this._uiHash()); + for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events + this._trigger("stop", event, this._uiHash()); + } + return false; + } + + if(!noPropagation) this._trigger("beforeStop", event, this._uiHash()); + + //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! + this.placeholder[0].parentNode.removeChild(this.placeholder[0]); + + if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null; + + if(!noPropagation) { + for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events + this._trigger("stop", event, this._uiHash()); + } + + this.fromOutside = false; + return true; + + }, + + _trigger: function() { + if ($.Widget.prototype._trigger.apply(this, arguments) === false) { + this.cancel(); + } + }, + + _uiHash: function(inst) { + var self = inst || this; + return { + helper: self.helper, + placeholder: self.placeholder || $([]), + position: self.position, + originalPosition: self.originalPosition, + offset: self.positionAbs, + item: self.currentItem, + sender: inst ? inst.element : null + }; + } + +}); + +$.extend($.ui.sortable, { + version: "1.8.11" +}); + +})(jQuery); +/* + * jQuery UI Effects 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/ + */ +;jQuery.effects || (function($, undefined) { + +$.effects = {}; + + + +/******************************************************************************/ +/****************************** COLOR ANIMATIONS ******************************/ +/******************************************************************************/ + +// override the animation for color styles +$.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor', + 'borderRightColor', 'borderTopColor', 'borderColor', 'color', 'outlineColor'], +function(i, attr) { + $.fx.step[attr] = function(fx) { + if (!fx.colorInit) { + fx.start = getColor(fx.elem, attr); + fx.end = getRGB(fx.end); + fx.colorInit = true; + } + + fx.elem.style[attr] = 'rgb(' + + Math.max(Math.min(parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0], 10), 255), 0) + ',' + + Math.max(Math.min(parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1], 10), 255), 0) + ',' + + Math.max(Math.min(parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2], 10), 255), 0) + ')'; + }; +}); + +// Color Conversion functions from highlightFade +// By Blair Mitchelmore +// http://jquery.offput.ca/highlightFade/ + +// Parse strings looking for color tuples [255,255,255] +function getRGB(color) { + var result; + + // Check if we're already dealing with an array of colors + if ( color && color.constructor == Array && color.length == 3 ) + return color; + + // Look for rgb(num,num,num) + if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) + return [parseInt(result[1],10), parseInt(result[2],10), parseInt(result[3],10)]; + + // Look for rgb(num%,num%,num%) + if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)) + return [parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55]; + + // Look for #a0b1c2 + if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)) + return [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)]; + + // Look for #fff + if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)) + return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)]; + + // Look for rgba(0, 0, 0, 0) == transparent in Safari 3 + if (result = /rgba\(0, 0, 0, 0\)/.exec(color)) + return colors['transparent']; + + // Otherwise, we're most likely dealing with a named color + return colors[$.trim(color).toLowerCase()]; +} + +function getColor(elem, attr) { + var color; + + do { + color = $.curCSS(elem, attr); + + // Keep going until we find an element that has color, or we hit the body + if ( color != '' && color != 'transparent' || $.nodeName(elem, "body") ) + break; + + attr = "backgroundColor"; + } while ( elem = elem.parentNode ); + + return getRGB(color); +}; + +// Some named colors to work with +// From Interface by Stefan Petre +// http://interface.eyecon.ro/ + +var colors = { + aqua:[0,255,255], + azure:[240,255,255], + beige:[245,245,220], + black:[0,0,0], + blue:[0,0,255], + brown:[165,42,42], + cyan:[0,255,255], + darkblue:[0,0,139], + darkcyan:[0,139,139], + darkgrey:[169,169,169], + darkgreen:[0,100,0], + darkkhaki:[189,183,107], + darkmagenta:[139,0,139], + darkolivegreen:[85,107,47], + darkorange:[255,140,0], + darkorchid:[153,50,204], + darkred:[139,0,0], + darksalmon:[233,150,122], + darkviolet:[148,0,211], + fuchsia:[255,0,255], + gold:[255,215,0], + green:[0,128,0], + indigo:[75,0,130], + khaki:[240,230,140], + lightblue:[173,216,230], + lightcyan:[224,255,255], + lightgreen:[144,238,144], + lightgrey:[211,211,211], + lightpink:[255,182,193], + lightyellow:[255,255,224], + lime:[0,255,0], + magenta:[255,0,255], + maroon:[128,0,0], + navy:[0,0,128], + olive:[128,128,0], + orange:[255,165,0], + pink:[255,192,203], + purple:[128,0,128], + violet:[128,0,128], + red:[255,0,0], + silver:[192,192,192], + white:[255,255,255], + yellow:[255,255,0], + transparent: [255,255,255] +}; + + + +/******************************************************************************/ +/****************************** CLASS ANIMATIONS ******************************/ +/******************************************************************************/ + +var classAnimationActions = ['add', 'remove', 'toggle'], + shorthandStyles = { + border: 1, + borderBottom: 1, + borderColor: 1, + borderLeft: 1, + borderRight: 1, + borderTop: 1, + borderWidth: 1, + margin: 1, + padding: 1 + }; + +function getElementStyles() { + var style = document.defaultView + ? document.defaultView.getComputedStyle(this, null) + : this.currentStyle, + newStyle = {}, + key, + camelCase; + + // webkit enumerates style porperties + if (style && style.length && style[0] && style[style[0]]) { + var len = style.length; + while (len--) { + key = style[len]; + if (typeof style[key] == 'string') { + camelCase = key.replace(/\-(\w)/g, function(all, letter){ + return letter.toUpperCase(); + }); + newStyle[camelCase] = style[key]; + } + } + } else { + for (key in style) { + if (typeof style[key] === 'string') { + newStyle[key] = style[key]; + } + } + } + + return newStyle; +} + +function filterStyles(styles) { + var name, value; + for (name in styles) { + value = styles[name]; + if ( + // ignore null and undefined values + value == null || + // ignore functions (when does this occur?) + $.isFunction(value) || + // shorthand styles that need to be expanded + name in shorthandStyles || + // ignore scrollbars (break in IE) + (/scrollbar/).test(name) || + + // only colors or values that can be converted to numbers + (!(/color/i).test(name) && isNaN(parseFloat(value))) + ) { + delete styles[name]; + } + } + + return styles; +} + +function styleDifference(oldStyle, newStyle) { + var diff = { _: 0 }, // http://dev.jquery.com/ticket/5459 + name; + + for (name in newStyle) { + if (oldStyle[name] != newStyle[name]) { + diff[name] = newStyle[name]; + } + } + + return diff; +} + +$.effects.animateClass = function(value, duration, easing, callback) { + if ($.isFunction(easing)) { + callback = easing; + easing = null; + } + + return this.queue('fx', function() { + var that = $(this), + originalStyleAttr = that.attr('style') || ' ', + originalStyle = filterStyles(getElementStyles.call(this)), + newStyle, + className = that.attr('className'); + + $.each(classAnimationActions, function(i, action) { + if (value[action]) { + that[action + 'Class'](value[action]); + } + }); + newStyle = filterStyles(getElementStyles.call(this)); + that.attr('className', className); + + that.animate(styleDifference(originalStyle, newStyle), duration, easing, function() { + $.each(classAnimationActions, function(i, action) { + if (value[action]) { that[action + 'Class'](value[action]); } + }); + // work around bug in IE by clearing the cssText before setting it + if (typeof that.attr('style') == 'object') { + that.attr('style').cssText = ''; + that.attr('style').cssText = originalStyleAttr; + } else { + that.attr('style', originalStyleAttr); + } + if (callback) { callback.apply(this, arguments); } + }); + + // $.animate adds a function to the end of the queue + // but we want it at the front + var queue = $.queue(this), + anim = queue.splice(queue.length - 1, 1)[0]; + queue.splice(1, 0, anim); + $.dequeue(this); + }); +}; + +$.fn.extend({ + _addClass: $.fn.addClass, + addClass: function(classNames, speed, easing, callback) { + return speed ? $.effects.animateClass.apply(this, [{ add: classNames },speed,easing,callback]) : this._addClass(classNames); + }, + + _removeClass: $.fn.removeClass, + removeClass: function(classNames,speed,easing,callback) { + return speed ? $.effects.animateClass.apply(this, [{ remove: classNames },speed,easing,callback]) : this._removeClass(classNames); + }, + + _toggleClass: $.fn.toggleClass, + toggleClass: function(classNames, force, speed, easing, callback) { + if ( typeof force == "boolean" || force === undefined ) { + if ( !speed ) { + // without speed parameter; + return this._toggleClass(classNames, force); + } else { + return $.effects.animateClass.apply(this, [(force?{add:classNames}:{remove:classNames}),speed,easing,callback]); + } + } else { + // without switch parameter; + return $.effects.animateClass.apply(this, [{ toggle: classNames },force,speed,easing]); + } + }, + + switchClass: function(remove,add,speed,easing,callback) { + return $.effects.animateClass.apply(this, [{ add: add, remove: remove },speed,easing,callback]); + } +}); + + + +/******************************************************************************/ +/*********************************** EFFECTS **********************************/ +/******************************************************************************/ + +$.extend($.effects, { + version: "1.8.11", + + // Saves a set of properties in a data storage + save: function(element, set) { + for(var i=0; i < set.length; i++) { + if(set[i] !== null) element.data("ec.storage."+set[i], element[0].style[set[i]]); + } + }, + + // Restores a set of previously saved properties from a data storage + restore: function(element, set) { + for(var i=0; i < set.length; i++) { + if(set[i] !== null) element.css(set[i], element.data("ec.storage."+set[i])); + } + }, + + setMode: function(el, mode) { + if (mode == 'toggle') mode = el.is(':hidden') ? 'show' : 'hide'; // Set for toggle + return mode; + }, + + getBaseline: function(origin, original) { // Translates a [top,left] array into a baseline value + // this should be a little more flexible in the future to handle a string & hash + var y, x; + switch (origin[0]) { + case 'top': y = 0; break; + case 'middle': y = 0.5; break; + case 'bottom': y = 1; break; + default: y = origin[0] / original.height; + }; + switch (origin[1]) { + case 'left': x = 0; break; + case 'center': x = 0.5; break; + case 'right': x = 1; break; + default: x = origin[1] / original.width; + }; + return {x: x, y: y}; + }, + + // Wraps the element around a wrapper that copies position properties + createWrapper: function(element) { + + // if the element is already wrapped, return it + if (element.parent().is('.ui-effects-wrapper')) { + return element.parent(); + } + + // wrap the element + var props = { + width: element.outerWidth(true), + height: element.outerHeight(true), + 'float': element.css('float') + }, + wrapper = $('
    ') + .addClass('ui-effects-wrapper') + .css({ + fontSize: '100%', + background: 'transparent', + border: 'none', + margin: 0, + padding: 0 + }); + + element.wrap(wrapper); + wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element + + // transfer positioning properties to the wrapper + if (element.css('position') == 'static') { + wrapper.css({ position: 'relative' }); + element.css({ position: 'relative' }); + } else { + $.extend(props, { + position: element.css('position'), + zIndex: element.css('z-index') + }); + $.each(['top', 'left', 'bottom', 'right'], function(i, pos) { + props[pos] = element.css(pos); + if (isNaN(parseInt(props[pos], 10))) { + props[pos] = 'auto'; + } + }); + element.css({position: 'relative', top: 0, left: 0, right: 'auto', bottom: 'auto' }); + } + + return wrapper.css(props).show(); + }, + + removeWrapper: function(element) { + if (element.parent().is('.ui-effects-wrapper')) + return element.parent().replaceWith(element); + return element; + }, + + setTransition: function(element, list, factor, value) { + value = value || {}; + $.each(list, function(i, x){ + unit = element.cssUnit(x); + if (unit[0] > 0) value[x] = unit[0] * factor + unit[1]; + }); + return value; + } +}); + + +function _normalizeArguments(effect, options, speed, callback) { + // shift params for method overloading + if (typeof effect == 'object') { + callback = options; + speed = null; + options = effect; + effect = options.effect; + } + if ($.isFunction(options)) { + callback = options; + speed = null; + options = {}; + } + if (typeof options == 'number' || $.fx.speeds[options]) { + callback = speed; + speed = options; + options = {}; + } + if ($.isFunction(speed)) { + callback = speed; + speed = null; + } + + options = options || {}; + + speed = speed || options.duration; + speed = $.fx.off ? 0 : typeof speed == 'number' + ? speed : speed in $.fx.speeds ? $.fx.speeds[speed] : $.fx.speeds._default; + + callback = callback || options.complete; + + return [effect, options, speed, callback]; +} + +function standardSpeed( speed ) { + // valid standard speeds + if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) { + return true; + } + + // invalid strings - treat as "normal" speed + if ( typeof speed === "string" && !$.effects[ speed ] ) { + return true; + } + + return false; +} + +$.fn.extend({ + effect: function(effect, options, speed, callback) { + var args = _normalizeArguments.apply(this, arguments), + // TODO: make effects take actual parameters instead of a hash + args2 = { + options: args[1], + duration: args[2], + callback: args[3] + }, + mode = args2.options.mode, + effectMethod = $.effects[effect]; + + if ( $.fx.off || !effectMethod ) { + // delegate to the original method (e.g., .show()) if possible + if ( mode ) { + return this[ mode ]( args2.duration, args2.callback ); + } else { + return this.each(function() { + if ( args2.callback ) { + args2.callback.call( this ); + } + }); + } + } + + return effectMethod.call(this, args2); + }, + + _show: $.fn.show, + show: function(speed) { + if ( standardSpeed( speed ) ) { + return this._show.apply(this, arguments); + } else { + var args = _normalizeArguments.apply(this, arguments); + args[1].mode = 'show'; + return this.effect.apply(this, args); + } + }, + + _hide: $.fn.hide, + hide: function(speed) { + if ( standardSpeed( speed ) ) { + return this._hide.apply(this, arguments); + } else { + var args = _normalizeArguments.apply(this, arguments); + args[1].mode = 'hide'; + return this.effect.apply(this, args); + } + }, + + // jQuery core overloads toggle and creates _toggle + __toggle: $.fn.toggle, + toggle: function(speed) { + if ( standardSpeed( speed ) || typeof speed === "boolean" || $.isFunction( speed ) ) { + return this.__toggle.apply(this, arguments); + } else { + var args = _normalizeArguments.apply(this, arguments); + args[1].mode = 'toggle'; + return this.effect.apply(this, args); + } + }, + + // helper functions + cssUnit: function(key) { + var style = this.css(key), val = []; + $.each( ['em','px','%','pt'], function(i, unit){ + if(style.indexOf(unit) > 0) + val = [parseFloat(style), unit]; + }); + return val; + } +}); + + + +/******************************************************************************/ +/*********************************** EASING ***********************************/ +/******************************************************************************/ + +/* + * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ + * + * Uses the built in easing capabilities added In jQuery 1.1 + * to offer multiple easing options + * + * TERMS OF USE - jQuery Easing + * + * Open source under the BSD License. + * + * Copyright 2008 George McGinley Smith + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the author nor the names of contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * +*/ + +// t: current time, b: begInnIng value, c: change In value, d: duration +$.easing.jswing = $.easing.swing; + +$.extend($.easing, +{ + def: 'easeOutQuad', + swing: function (x, t, b, c, d) { + //alert($.easing.default); + return $.easing[$.easing.def](x, t, b, c, d); + }, + easeInQuad: function (x, t, b, c, d) { + return c*(t/=d)*t + b; + }, + easeOutQuad: function (x, t, b, c, d) { + return -c *(t/=d)*(t-2) + b; + }, + easeInOutQuad: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t + b; + return -c/2 * ((--t)*(t-2) - 1) + b; + }, + easeInCubic: function (x, t, b, c, d) { + return c*(t/=d)*t*t + b; + }, + easeOutCubic: function (x, t, b, c, d) { + return c*((t=t/d-1)*t*t + 1) + b; + }, + easeInOutCubic: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t + b; + return c/2*((t-=2)*t*t + 2) + b; + }, + easeInQuart: function (x, t, b, c, d) { + return c*(t/=d)*t*t*t + b; + }, + easeOutQuart: function (x, t, b, c, d) { + return -c * ((t=t/d-1)*t*t*t - 1) + b; + }, + easeInOutQuart: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t*t + b; + return -c/2 * ((t-=2)*t*t*t - 2) + b; + }, + easeInQuint: function (x, t, b, c, d) { + return c*(t/=d)*t*t*t*t + b; + }, + easeOutQuint: function (x, t, b, c, d) { + return c*((t=t/d-1)*t*t*t*t + 1) + b; + }, + easeInOutQuint: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b; + return c/2*((t-=2)*t*t*t*t + 2) + b; + }, + easeInSine: function (x, t, b, c, d) { + return -c * Math.cos(t/d * (Math.PI/2)) + c + b; + }, + easeOutSine: function (x, t, b, c, d) { + return c * Math.sin(t/d * (Math.PI/2)) + b; + }, + easeInOutSine: function (x, t, b, c, d) { + return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b; + }, + easeInExpo: function (x, t, b, c, d) { + return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; + }, + easeOutExpo: function (x, t, b, c, d) { + return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; + }, + easeInOutExpo: function (x, t, b, c, d) { + if (t==0) return b; + if (t==d) return b+c; + if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; + return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; + }, + easeInCirc: function (x, t, b, c, d) { + return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b; + }, + easeOutCirc: function (x, t, b, c, d) { + return c * Math.sqrt(1 - (t=t/d-1)*t) + b; + }, + easeInOutCirc: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b; + return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b; + }, + easeInElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; + }, + easeOutElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b; + }, + easeInOutElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5); + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; + return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b; + }, + easeInBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c*(t/=d)*t*((s+1)*t - s) + b; + }, + easeOutBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; + }, + easeInOutBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; + return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; + }, + easeInBounce: function (x, t, b, c, d) { + return c - $.easing.easeOutBounce (x, d-t, 0, c, d) + b; + }, + easeOutBounce: function (x, t, b, c, d) { + if ((t/=d) < (1/2.75)) { + return c*(7.5625*t*t) + b; + } else if (t < (2/2.75)) { + return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; + } else if (t < (2.5/2.75)) { + return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; + } else { + return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; + } + }, + easeInOutBounce: function (x, t, b, c, d) { + if (t < d/2) return $.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b; + return $.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b; + } +}); + +/* + * + * TERMS OF USE - EASING EQUATIONS + * + * Open source under the BSD License. + * + * Copyright 2001 Robert Penner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the author nor the names of contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +})(jQuery); +/* + * jQuery UI Effects Blind 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Blind + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.blind = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position','top','bottom','left','right']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode + var direction = o.options.direction || 'vertical'; // Default direction + + // Adjust + $.effects.save(el, props); el.show(); // Save & Show + var wrapper = $.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper + var ref = (direction == 'vertical') ? 'height' : 'width'; + var distance = (direction == 'vertical') ? wrapper.height() : wrapper.width(); + if(mode == 'show') wrapper.css(ref, 0); // Shift + + // Animation + var animation = {}; + animation[ref] = mode == 'show' ? distance : 0; + + // Animate + wrapper.animate(animation, o.duration, o.options.easing, function() { + if(mode == 'hide') el.hide(); // Hide + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if(o.callback) o.callback.apply(el[0], arguments); // Callback + el.dequeue(); + }); + + }); + +}; + +})(jQuery); +/* + * jQuery UI Effects Bounce 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Bounce + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.bounce = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position','top','bottom','left','right']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode + var direction = o.options.direction || 'up'; // Default direction + var distance = o.options.distance || 20; // Default distance + var times = o.options.times || 5; // Default # of times + var speed = o.duration || 250; // Default speed per bounce + if (/show|hide/.test(mode)) props.push('opacity'); // Avoid touching opacity to prevent clearType and PNG issues in IE + + // Adjust + $.effects.save(el, props); el.show(); // Save & Show + $.effects.createWrapper(el); // Create Wrapper + var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left'; + var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg'; + var distance = o.options.distance || (ref == 'top' ? el.outerHeight({margin:true}) / 3 : el.outerWidth({margin:true}) / 3); + if (mode == 'show') el.css('opacity', 0).css(ref, motion == 'pos' ? -distance : distance); // Shift + if (mode == 'hide') distance = distance / (times * 2); + if (mode != 'hide') times--; + + // Animate + if (mode == 'show') { // Show Bounce + var animation = {opacity: 1}; + animation[ref] = (motion == 'pos' ? '+=' : '-=') + distance; + el.animate(animation, speed / 2, o.options.easing); + distance = distance / 2; + times--; + }; + for (var i = 0; i < times; i++) { // Bounces + var animation1 = {}, animation2 = {}; + animation1[ref] = (motion == 'pos' ? '-=' : '+=') + distance; + animation2[ref] = (motion == 'pos' ? '+=' : '-=') + distance; + el.animate(animation1, speed / 2, o.options.easing).animate(animation2, speed / 2, o.options.easing); + distance = (mode == 'hide') ? distance * 2 : distance / 2; + }; + if (mode == 'hide') { // Last Bounce + var animation = {opacity: 0}; + animation[ref] = (motion == 'pos' ? '-=' : '+=') + distance; + el.animate(animation, speed / 2, o.options.easing, function(){ + el.hide(); // Hide + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if(o.callback) o.callback.apply(this, arguments); // Callback + }); + } else { + var animation1 = {}, animation2 = {}; + animation1[ref] = (motion == 'pos' ? '-=' : '+=') + distance; + animation2[ref] = (motion == 'pos' ? '+=' : '-=') + distance; + el.animate(animation1, speed / 2, o.options.easing).animate(animation2, speed / 2, o.options.easing, function(){ + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if(o.callback) o.callback.apply(this, arguments); // Callback + }); + }; + el.queue('fx', function() { el.dequeue(); }); + el.dequeue(); + }); + +}; + +})(jQuery); +/* + * jQuery UI Effects Clip 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Clip + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.clip = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position','top','bottom','left','right','height','width']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode + var direction = o.options.direction || 'vertical'; // Default direction + + // Adjust + $.effects.save(el, props); el.show(); // Save & Show + var wrapper = $.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper + var animate = el[0].tagName == 'IMG' ? wrapper : el; + var ref = { + size: (direction == 'vertical') ? 'height' : 'width', + position: (direction == 'vertical') ? 'top' : 'left' + }; + var distance = (direction == 'vertical') ? animate.height() : animate.width(); + if(mode == 'show') { animate.css(ref.size, 0); animate.css(ref.position, distance / 2); } // Shift + + // Animation + var animation = {}; + animation[ref.size] = mode == 'show' ? distance : 0; + animation[ref.position] = mode == 'show' ? 0 : distance / 2; + + // Animate + animate.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() { + if(mode == 'hide') el.hide(); // Hide + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if(o.callback) o.callback.apply(el[0], arguments); // Callback + el.dequeue(); + }}); + + }); + +}; + +})(jQuery); +/* + * jQuery UI Effects Drop 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Drop + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.drop = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position','top','bottom','left','right','opacity']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode + var direction = o.options.direction || 'left'; // Default Direction + + // Adjust + $.effects.save(el, props); el.show(); // Save & Show + $.effects.createWrapper(el); // Create Wrapper + var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left'; + var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg'; + var distance = o.options.distance || (ref == 'top' ? el.outerHeight({margin:true}) / 2 : el.outerWidth({margin:true}) / 2); + if (mode == 'show') el.css('opacity', 0).css(ref, motion == 'pos' ? -distance : distance); // Shift + + // Animation + var animation = {opacity: mode == 'show' ? 1 : 0}; + animation[ref] = (mode == 'show' ? (motion == 'pos' ? '+=' : '-=') : (motion == 'pos' ? '-=' : '+=')) + distance; + + // Animate + el.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() { + if(mode == 'hide') el.hide(); // Hide + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if(o.callback) o.callback.apply(this, arguments); // Callback + el.dequeue(); + }}); + + }); + +}; + +})(jQuery); +/* + * jQuery UI Effects Explode 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Explode + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.explode = function(o) { + + return this.queue(function() { + + var rows = o.options.pieces ? Math.round(Math.sqrt(o.options.pieces)) : 3; + var cells = o.options.pieces ? Math.round(Math.sqrt(o.options.pieces)) : 3; + + o.options.mode = o.options.mode == 'toggle' ? ($(this).is(':visible') ? 'hide' : 'show') : o.options.mode; + var el = $(this).show().css('visibility', 'hidden'); + var offset = el.offset(); + + //Substract the margins - not fixing the problem yet. + offset.top -= parseInt(el.css("marginTop"),10) || 0; + offset.left -= parseInt(el.css("marginLeft"),10) || 0; + + var width = el.outerWidth(true); + var height = el.outerHeight(true); + + for(var i=0;i') + .css({ + position: 'absolute', + visibility: 'visible', + left: -j*(width/cells), + top: -i*(height/rows) + }) + .parent() + .addClass('ui-effects-explode') + .css({ + position: 'absolute', + overflow: 'hidden', + width: width/cells, + height: height/rows, + left: offset.left + j*(width/cells) + (o.options.mode == 'show' ? (j-Math.floor(cells/2))*(width/cells) : 0), + top: offset.top + i*(height/rows) + (o.options.mode == 'show' ? (i-Math.floor(rows/2))*(height/rows) : 0), + opacity: o.options.mode == 'show' ? 0 : 1 + }).animate({ + left: offset.left + j*(width/cells) + (o.options.mode == 'show' ? 0 : (j-Math.floor(cells/2))*(width/cells)), + top: offset.top + i*(height/rows) + (o.options.mode == 'show' ? 0 : (i-Math.floor(rows/2))*(height/rows)), + opacity: o.options.mode == 'show' ? 1 : 0 + }, o.duration || 500); + } + } + + // Set a timeout, to call the callback approx. when the other animations have finished + setTimeout(function() { + + o.options.mode == 'show' ? el.css({ visibility: 'visible' }) : el.css({ visibility: 'visible' }).hide(); + if(o.callback) o.callback.apply(el[0]); // Callback + el.dequeue(); + + $('div.ui-effects-explode').remove(); + + }, o.duration || 500); + + + }); + +}; + +})(jQuery); +/* + * jQuery UI Effects Fade 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Fade + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.fade = function(o) { + return this.queue(function() { + var elem = $(this), + mode = $.effects.setMode(elem, o.options.mode || 'hide'); + + elem.animate({ opacity: mode }, { + queue: false, + duration: o.duration, + easing: o.options.easing, + complete: function() { + (o.callback && o.callback.apply(this, arguments)); + elem.dequeue(); + } + }); + }); +}; + +})(jQuery); +/* + * jQuery UI Effects Fold 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Fold + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.fold = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position','top','bottom','left','right']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode + var size = o.options.size || 15; // Default fold size + var horizFirst = !(!o.options.horizFirst); // Ensure a boolean value + var duration = o.duration ? o.duration / 2 : $.fx.speeds._default / 2; + + // Adjust + $.effects.save(el, props); el.show(); // Save & Show + var wrapper = $.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper + var widthFirst = ((mode == 'show') != horizFirst); + var ref = widthFirst ? ['width', 'height'] : ['height', 'width']; + var distance = widthFirst ? [wrapper.width(), wrapper.height()] : [wrapper.height(), wrapper.width()]; + var percent = /([0-9]+)%/.exec(size); + if(percent) size = parseInt(percent[1],10) / 100 * distance[mode == 'hide' ? 0 : 1]; + if(mode == 'show') wrapper.css(horizFirst ? {height: 0, width: size} : {height: size, width: 0}); // Shift + + // Animation + var animation1 = {}, animation2 = {}; + animation1[ref[0]] = mode == 'show' ? distance[0] : size; + animation2[ref[1]] = mode == 'show' ? distance[1] : 0; + + // Animate + wrapper.animate(animation1, duration, o.options.easing) + .animate(animation2, duration, o.options.easing, function() { + if(mode == 'hide') el.hide(); // Hide + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if(o.callback) o.callback.apply(el[0], arguments); // Callback + el.dequeue(); + }); + + }); + +}; + +})(jQuery); +/* + * jQuery UI Effects Highlight 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Highlight + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.highlight = function(o) { + return this.queue(function() { + var elem = $(this), + props = ['backgroundImage', 'backgroundColor', 'opacity'], + mode = $.effects.setMode(elem, o.options.mode || 'show'), + animation = { + backgroundColor: elem.css('backgroundColor') + }; + + if (mode == 'hide') { + animation.opacity = 0; + } + + $.effects.save(elem, props); + elem + .show() + .css({ + backgroundImage: 'none', + backgroundColor: o.options.color || '#ffff99' + }) + .animate(animation, { + queue: false, + duration: o.duration, + easing: o.options.easing, + complete: function() { + (mode == 'hide' && elem.hide()); + $.effects.restore(elem, props); + (mode == 'show' && !$.support.opacity && this.style.removeAttribute('filter')); + (o.callback && o.callback.apply(this, arguments)); + elem.dequeue(); + } + }); + }); +}; + +})(jQuery); +/* + * jQuery UI Effects Pulsate 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Pulsate + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.pulsate = function(o) { + return this.queue(function() { + var elem = $(this), + mode = $.effects.setMode(elem, o.options.mode || 'show'); + times = ((o.options.times || 5) * 2) - 1; + duration = o.duration ? o.duration / 2 : $.fx.speeds._default / 2, + isVisible = elem.is(':visible'), + animateTo = 0; + + if (!isVisible) { + elem.css('opacity', 0).show(); + animateTo = 1; + } + + if ((mode == 'hide' && isVisible) || (mode == 'show' && !isVisible)) { + times--; + } + + for (var i = 0; i < times; i++) { + elem.animate({ opacity: animateTo }, duration, o.options.easing); + animateTo = (animateTo + 1) % 2; + } + + elem.animate({ opacity: animateTo }, duration, o.options.easing, function() { + if (animateTo == 0) { + elem.hide(); + } + (o.callback && o.callback.apply(this, arguments)); + }); + + elem + .queue('fx', function() { elem.dequeue(); }) + .dequeue(); + }); +}; + +})(jQuery); +/* + * jQuery UI Effects Scale 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Scale + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.puff = function(o) { + return this.queue(function() { + var elem = $(this), + mode = $.effects.setMode(elem, o.options.mode || 'hide'), + percent = parseInt(o.options.percent, 10) || 150, + factor = percent / 100, + original = { height: elem.height(), width: elem.width() }; + + $.extend(o.options, { + fade: true, + mode: mode, + percent: mode == 'hide' ? percent : 100, + from: mode == 'hide' + ? original + : { + height: original.height * factor, + width: original.width * factor + } + }); + + elem.effect('scale', o.options, o.duration, o.callback); + elem.dequeue(); + }); +}; + +$.effects.scale = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this); + + // Set options + var options = $.extend(true, {}, o.options); + var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode + var percent = parseInt(o.options.percent,10) || (parseInt(o.options.percent,10) == 0 ? 0 : (mode == 'hide' ? 0 : 100)); // Set default scaling percent + var direction = o.options.direction || 'both'; // Set default axis + var origin = o.options.origin; // The origin of the scaling + if (mode != 'effect') { // Set default origin and restore for show/hide + options.origin = origin || ['middle','center']; + options.restore = true; + } + var original = {height: el.height(), width: el.width()}; // Save original + el.from = o.options.from || (mode == 'show' ? {height: 0, width: 0} : original); // Default from state + + // Adjust + var factor = { // Set scaling factor + y: direction != 'horizontal' ? (percent / 100) : 1, + x: direction != 'vertical' ? (percent / 100) : 1 + }; + el.to = {height: original.height * factor.y, width: original.width * factor.x}; // Set to state + + if (o.options.fade) { // Fade option to support puff + if (mode == 'show') {el.from.opacity = 0; el.to.opacity = 1;}; + if (mode == 'hide') {el.from.opacity = 1; el.to.opacity = 0;}; + }; + + // Animation + options.from = el.from; options.to = el.to; options.mode = mode; + + // Animate + el.effect('size', options, o.duration, o.callback); + el.dequeue(); + }); + +}; + +$.effects.size = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position','top','bottom','left','right','width','height','overflow','opacity']; + var props1 = ['position','top','bottom','left','right','overflow','opacity']; // Always restore + var props2 = ['width','height','overflow']; // Copy for children + var cProps = ['fontSize']; + var vProps = ['borderTopWidth', 'borderBottomWidth', 'paddingTop', 'paddingBottom']; + var hProps = ['borderLeftWidth', 'borderRightWidth', 'paddingLeft', 'paddingRight']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode + var restore = o.options.restore || false; // Default restore + var scale = o.options.scale || 'both'; // Default scale mode + var origin = o.options.origin; // The origin of the sizing + var original = {height: el.height(), width: el.width()}; // Save original + el.from = o.options.from || original; // Default from state + el.to = o.options.to || original; // Default to state + // Adjust + if (origin) { // Calculate baseline shifts + var baseline = $.effects.getBaseline(origin, original); + el.from.top = (original.height - el.from.height) * baseline.y; + el.from.left = (original.width - el.from.width) * baseline.x; + el.to.top = (original.height - el.to.height) * baseline.y; + el.to.left = (original.width - el.to.width) * baseline.x; + }; + var factor = { // Set scaling factor + from: {y: el.from.height / original.height, x: el.from.width / original.width}, + to: {y: el.to.height / original.height, x: el.to.width / original.width} + }; + if (scale == 'box' || scale == 'both') { // Scale the css box + if (factor.from.y != factor.to.y) { // Vertical props scaling + props = props.concat(vProps); + el.from = $.effects.setTransition(el, vProps, factor.from.y, el.from); + el.to = $.effects.setTransition(el, vProps, factor.to.y, el.to); + }; + if (factor.from.x != factor.to.x) { // Horizontal props scaling + props = props.concat(hProps); + el.from = $.effects.setTransition(el, hProps, factor.from.x, el.from); + el.to = $.effects.setTransition(el, hProps, factor.to.x, el.to); + }; + }; + if (scale == 'content' || scale == 'both') { // Scale the content + if (factor.from.y != factor.to.y) { // Vertical props scaling + props = props.concat(cProps); + el.from = $.effects.setTransition(el, cProps, factor.from.y, el.from); + el.to = $.effects.setTransition(el, cProps, factor.to.y, el.to); + }; + }; + $.effects.save(el, restore ? props : props1); el.show(); // Save & Show + $.effects.createWrapper(el); // Create Wrapper + el.css('overflow','hidden').css(el.from); // Shift + + // Animate + if (scale == 'content' || scale == 'both') { // Scale the children + vProps = vProps.concat(['marginTop','marginBottom']).concat(cProps); // Add margins/font-size + hProps = hProps.concat(['marginLeft','marginRight']); // Add margins + props2 = props.concat(vProps).concat(hProps); // Concat + el.find("*[width]").each(function(){ + child = $(this); + if (restore) $.effects.save(child, props2); + var c_original = {height: child.height(), width: child.width()}; // Save original + child.from = {height: c_original.height * factor.from.y, width: c_original.width * factor.from.x}; + child.to = {height: c_original.height * factor.to.y, width: c_original.width * factor.to.x}; + if (factor.from.y != factor.to.y) { // Vertical props scaling + child.from = $.effects.setTransition(child, vProps, factor.from.y, child.from); + child.to = $.effects.setTransition(child, vProps, factor.to.y, child.to); + }; + if (factor.from.x != factor.to.x) { // Horizontal props scaling + child.from = $.effects.setTransition(child, hProps, factor.from.x, child.from); + child.to = $.effects.setTransition(child, hProps, factor.to.x, child.to); + }; + child.css(child.from); // Shift children + child.animate(child.to, o.duration, o.options.easing, function(){ + if (restore) $.effects.restore(child, props2); // Restore children + }); // Animate children + }); + }; + + // Animate + el.animate(el.to, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() { + if (el.to.opacity === 0) { + el.css('opacity', el.from.opacity); + } + if(mode == 'hide') el.hide(); // Hide + $.effects.restore(el, restore ? props : props1); $.effects.removeWrapper(el); // Restore + if(o.callback) o.callback.apply(this, arguments); // Callback + el.dequeue(); + }}); + + }); + +}; + +})(jQuery); +/* + * jQuery UI Effects Shake 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Shake + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.shake = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position','top','bottom','left','right']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode + var direction = o.options.direction || 'left'; // Default direction + var distance = o.options.distance || 20; // Default distance + var times = o.options.times || 3; // Default # of times + var speed = o.duration || o.options.duration || 140; // Default speed per shake + + // Adjust + $.effects.save(el, props); el.show(); // Save & Show + $.effects.createWrapper(el); // Create Wrapper + var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left'; + var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg'; + + // Animation + var animation = {}, animation1 = {}, animation2 = {}; + animation[ref] = (motion == 'pos' ? '-=' : '+=') + distance; + animation1[ref] = (motion == 'pos' ? '+=' : '-=') + distance * 2; + animation2[ref] = (motion == 'pos' ? '-=' : '+=') + distance * 2; + + // Animate + el.animate(animation, speed, o.options.easing); + for (var i = 1; i < times; i++) { // Shakes + el.animate(animation1, speed, o.options.easing).animate(animation2, speed, o.options.easing); + }; + el.animate(animation1, speed, o.options.easing). + animate(animation, speed / 2, o.options.easing, function(){ // Last shake + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if(o.callback) o.callback.apply(this, arguments); // Callback + }); + el.queue('fx', function() { el.dequeue(); }); + el.dequeue(); + }); + +}; + +})(jQuery); +/* + * jQuery UI Effects Slide 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Slide + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.slide = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position','top','bottom','left','right']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'show'); // Set Mode + var direction = o.options.direction || 'left'; // Default Direction + + // Adjust + $.effects.save(el, props); el.show(); // Save & Show + $.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper + var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left'; + var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg'; + var distance = o.options.distance || (ref == 'top' ? el.outerHeight({margin:true}) : el.outerWidth({margin:true})); + if (mode == 'show') el.css(ref, motion == 'pos' ? (isNaN(distance) ? "-" + distance : -distance) : distance); // Shift + + // Animation + var animation = {}; + animation[ref] = (mode == 'show' ? (motion == 'pos' ? '+=' : '-=') : (motion == 'pos' ? '-=' : '+=')) + distance; + + // Animate + el.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() { + if(mode == 'hide') el.hide(); // Hide + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if(o.callback) o.callback.apply(this, arguments); // Callback + el.dequeue(); + }}); + + }); + +}; + +})(jQuery); +/* + * jQuery UI Effects Transfer 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Transfer + * + * Depends: + * jquery.effects.core.js + */ +(function( $, undefined ) { + +$.effects.transfer = function(o) { + return this.queue(function() { + var elem = $(this), + target = $(o.options.to), + endPosition = target.offset(), + animation = { + top: endPosition.top, + left: endPosition.left, + height: target.innerHeight(), + width: target.innerWidth() + }, + startPosition = elem.offset(), + transfer = $('
    ') + .appendTo(document.body) + .addClass(o.options.className) + .css({ + top: startPosition.top, + left: startPosition.left, + height: elem.innerHeight(), + width: elem.innerWidth(), + position: 'absolute' + }) + .animate(animation, o.duration, o.options.easing, function() { + transfer.remove(); + (o.callback && o.callback.apply(elem[0], arguments)); + elem.dequeue(); + }); + }); +}; + +})(jQuery); +/* + * jQuery UI Accordion 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Accordion + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +$.widget( "ui.accordion", { + options: { + active: 0, + animated: "slide", + autoHeight: true, + clearStyle: false, + collapsible: false, + event: "click", + fillSpace: false, + header: "> li > :first-child,> :not(li):even", + icons: { + header: "ui-icon-triangle-1-e", + headerSelected: "ui-icon-triangle-1-s" + }, + navigation: false, + navigationFilter: function() { + return this.href.toLowerCase() === location.href.toLowerCase(); + } + }, + + _create: function() { + var self = this, + options = self.options; + + self.running = 0; + + self.element + .addClass( "ui-accordion ui-widget ui-helper-reset" ) + // in lack of child-selectors in CSS + // we need to mark top-LIs in a UL-accordion for some IE-fix + .children( "li" ) + .addClass( "ui-accordion-li-fix" ); + + self.headers = self.element.find( options.header ) + .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" ) + .bind( "mouseenter.accordion", function() { + if ( options.disabled ) { + return; + } + $( this ).addClass( "ui-state-hover" ); + }) + .bind( "mouseleave.accordion", function() { + if ( options.disabled ) { + return; + } + $( this ).removeClass( "ui-state-hover" ); + }) + .bind( "focus.accordion", function() { + if ( options.disabled ) { + return; + } + $( this ).addClass( "ui-state-focus" ); + }) + .bind( "blur.accordion", function() { + if ( options.disabled ) { + return; + } + $( this ).removeClass( "ui-state-focus" ); + }); + + self.headers.next() + .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" ); + + if ( options.navigation ) { + var current = self.element.find( "a" ).filter( options.navigationFilter ).eq( 0 ); + if ( current.length ) { + var header = current.closest( ".ui-accordion-header" ); + if ( header.length ) { + // anchor within header + self.active = header; + } else { + // anchor within content + self.active = current.closest( ".ui-accordion-content" ).prev(); + } + } + } + + self.active = self._findActive( self.active || options.active ) + .addClass( "ui-state-default ui-state-active" ) + .toggleClass( "ui-corner-all" ) + .toggleClass( "ui-corner-top" ); + self.active.next().addClass( "ui-accordion-content-active" ); + + self._createIcons(); + self.resize(); + + // ARIA + self.element.attr( "role", "tablist" ); + + self.headers + .attr( "role", "tab" ) + .bind( "keydown.accordion", function( event ) { + return self._keydown( event ); + }) + .next() + .attr( "role", "tabpanel" ); + + self.headers + .not( self.active || "" ) + .attr({ + "aria-expanded": "false", + "aria-selected": "false", + tabIndex: -1 + }) + .next() + .hide(); + + // make sure at least one header is in the tab order + if ( !self.active.length ) { + self.headers.eq( 0 ).attr( "tabIndex", 0 ); + } else { + self.active + .attr({ + "aria-expanded": "true", + "aria-selected": "true", + tabIndex: 0 + }); + } + + // only need links in tab order for Safari + if ( !$.browser.safari ) { + self.headers.find( "a" ).attr( "tabIndex", -1 ); + } + + if ( options.event ) { + self.headers.bind( options.event.split(" ").join(".accordion ") + ".accordion", function(event) { + self._clickHandler.call( self, event, this ); + event.preventDefault(); + }); + } + }, + + _createIcons: function() { + var options = this.options; + if ( options.icons ) { + $( "" ) + .addClass( "ui-icon " + options.icons.header ) + .prependTo( this.headers ); + this.active.children( ".ui-icon" ) + .toggleClass(options.icons.header) + .toggleClass(options.icons.headerSelected); + this.element.addClass( "ui-accordion-icons" ); + } + }, + + _destroyIcons: function() { + this.headers.children( ".ui-icon" ).remove(); + this.element.removeClass( "ui-accordion-icons" ); + }, + + destroy: function() { + var options = this.options; + + this.element + .removeClass( "ui-accordion ui-widget ui-helper-reset" ) + .removeAttr( "role" ); + + this.headers + .unbind( ".accordion" ) + .removeClass( "ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" ) + .removeAttr( "role" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-selected" ) + .removeAttr( "tabIndex" ); + + this.headers.find( "a" ).removeAttr( "tabIndex" ); + this._destroyIcons(); + var contents = this.headers.next() + .css( "display", "" ) + .removeAttr( "role" ) + .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" ); + if ( options.autoHeight || options.fillHeight ) { + contents.css( "height", "" ); + } + + return $.Widget.prototype.destroy.call( this ); + }, + + _setOption: function( key, value ) { + $.Widget.prototype._setOption.apply( this, arguments ); + + if ( key == "active" ) { + this.activate( value ); + } + if ( key == "icons" ) { + this._destroyIcons(); + if ( value ) { + this._createIcons(); + } + } + // #5332 - opacity doesn't cascade to positioned elements in IE + // so we need to add the disabled class to the headers and panels + if ( key == "disabled" ) { + this.headers.add(this.headers.next()) + [ value ? "addClass" : "removeClass" ]( + "ui-accordion-disabled ui-state-disabled" ); + } + }, + + _keydown: function( event ) { + if ( this.options.disabled || event.altKey || event.ctrlKey ) { + return; + } + + var keyCode = $.ui.keyCode, + length = this.headers.length, + currentIndex = this.headers.index( event.target ), + toFocus = false; + + switch ( event.keyCode ) { + case keyCode.RIGHT: + case keyCode.DOWN: + toFocus = this.headers[ ( currentIndex + 1 ) % length ]; + break; + case keyCode.LEFT: + case keyCode.UP: + toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; + break; + case keyCode.SPACE: + case keyCode.ENTER: + this._clickHandler( { target: event.target }, event.target ); + event.preventDefault(); + } + + if ( toFocus ) { + $( event.target ).attr( "tabIndex", -1 ); + $( toFocus ).attr( "tabIndex", 0 ); + toFocus.focus(); + return false; + } + + return true; + }, + + resize: function() { + var options = this.options, + maxHeight; + + if ( options.fillSpace ) { + if ( $.browser.msie ) { + var defOverflow = this.element.parent().css( "overflow" ); + this.element.parent().css( "overflow", "hidden"); + } + maxHeight = this.element.parent().height(); + if ($.browser.msie) { + this.element.parent().css( "overflow", defOverflow ); + } + + this.headers.each(function() { + maxHeight -= $( this ).outerHeight( true ); + }); + + this.headers.next() + .each(function() { + $( this ).height( Math.max( 0, maxHeight - + $( this ).innerHeight() + $( this ).height() ) ); + }) + .css( "overflow", "auto" ); + } else if ( options.autoHeight ) { + maxHeight = 0; + this.headers.next() + .each(function() { + maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() ); + }) + .height( maxHeight ); + } + + return this; + }, + + activate: function( index ) { + // TODO this gets called on init, changing the option without an explicit call for that + this.options.active = index; + // call clickHandler with custom event + var active = this._findActive( index )[ 0 ]; + this._clickHandler( { target: active }, active ); + + return this; + }, + + _findActive: function( selector ) { + return selector + ? typeof selector === "number" + ? this.headers.filter( ":eq(" + selector + ")" ) + : this.headers.not( this.headers.not( selector ) ) + : selector === false + ? $( [] ) + : this.headers.filter( ":eq(0)" ); + }, + + // TODO isn't event.target enough? why the separate target argument? + _clickHandler: function( event, target ) { + var options = this.options; + if ( options.disabled ) { + return; + } + + // called only when using activate(false) to close all parts programmatically + if ( !event.target ) { + if ( !options.collapsible ) { + return; + } + this.active + .removeClass( "ui-state-active ui-corner-top" ) + .addClass( "ui-state-default ui-corner-all" ) + .children( ".ui-icon" ) + .removeClass( options.icons.headerSelected ) + .addClass( options.icons.header ); + this.active.next().addClass( "ui-accordion-content-active" ); + var toHide = this.active.next(), + data = { + options: options, + newHeader: $( [] ), + oldHeader: options.active, + newContent: $( [] ), + oldContent: toHide + }, + toShow = ( this.active = $( [] ) ); + this._toggle( toShow, toHide, data ); + return; + } + + // get the click target + var clicked = $( event.currentTarget || target ), + clickedIsActive = clicked[0] === this.active[0]; + + // TODO the option is changed, is that correct? + // TODO if it is correct, shouldn't that happen after determining that the click is valid? + options.active = options.collapsible && clickedIsActive ? + false : + this.headers.index( clicked ); + + // if animations are still active, or the active header is the target, ignore click + if ( this.running || ( !options.collapsible && clickedIsActive ) ) { + return; + } + + // find elements to show and hide + var active = this.active, + toShow = clicked.next(), + toHide = this.active.next(), + data = { + options: options, + newHeader: clickedIsActive && options.collapsible ? $([]) : clicked, + oldHeader: this.active, + newContent: clickedIsActive && options.collapsible ? $([]) : toShow, + oldContent: toHide + }, + down = this.headers.index( this.active[0] ) > this.headers.index( clicked[0] ); + + // when the call to ._toggle() comes after the class changes + // it causes a very odd bug in IE 8 (see #6720) + this.active = clickedIsActive ? $([]) : clicked; + this._toggle( toShow, toHide, data, clickedIsActive, down ); + + // switch classes + active + .removeClass( "ui-state-active ui-corner-top" ) + .addClass( "ui-state-default ui-corner-all" ) + .children( ".ui-icon" ) + .removeClass( options.icons.headerSelected ) + .addClass( options.icons.header ); + if ( !clickedIsActive ) { + clicked + .removeClass( "ui-state-default ui-corner-all" ) + .addClass( "ui-state-active ui-corner-top" ) + .children( ".ui-icon" ) + .removeClass( options.icons.header ) + .addClass( options.icons.headerSelected ); + clicked + .next() + .addClass( "ui-accordion-content-active" ); + } + + return; + }, + + _toggle: function( toShow, toHide, data, clickedIsActive, down ) { + var self = this, + options = self.options; + + self.toShow = toShow; + self.toHide = toHide; + self.data = data; + + var complete = function() { + if ( !self ) { + return; + } + return self._completed.apply( self, arguments ); + }; + + // trigger changestart event + self._trigger( "changestart", null, self.data ); + + // count elements to animate + self.running = toHide.size() === 0 ? toShow.size() : toHide.size(); + + if ( options.animated ) { + var animOptions = {}; + + if ( options.collapsible && clickedIsActive ) { + animOptions = { + toShow: $( [] ), + toHide: toHide, + complete: complete, + down: down, + autoHeight: options.autoHeight || options.fillSpace + }; + } else { + animOptions = { + toShow: toShow, + toHide: toHide, + complete: complete, + down: down, + autoHeight: options.autoHeight || options.fillSpace + }; + } + + if ( !options.proxied ) { + options.proxied = options.animated; + } + + if ( !options.proxiedDuration ) { + options.proxiedDuration = options.duration; + } + + options.animated = $.isFunction( options.proxied ) ? + options.proxied( animOptions ) : + options.proxied; + + options.duration = $.isFunction( options.proxiedDuration ) ? + options.proxiedDuration( animOptions ) : + options.proxiedDuration; + + var animations = $.ui.accordion.animations, + duration = options.duration, + easing = options.animated; + + if ( easing && !animations[ easing ] && !$.easing[ easing ] ) { + easing = "slide"; + } + if ( !animations[ easing ] ) { + animations[ easing ] = function( options ) { + this.slide( options, { + easing: easing, + duration: duration || 700 + }); + }; + } + + animations[ easing ]( animOptions ); + } else { + if ( options.collapsible && clickedIsActive ) { + toShow.toggle(); + } else { + toHide.hide(); + toShow.show(); + } + + complete( true ); + } + + // TODO assert that the blur and focus triggers are really necessary, remove otherwise + toHide.prev() + .attr({ + "aria-expanded": "false", + "aria-selected": "false", + tabIndex: -1 + }) + .blur(); + toShow.prev() + .attr({ + "aria-expanded": "true", + "aria-selected": "true", + tabIndex: 0 + }) + .focus(); + }, + + _completed: function( cancel ) { + this.running = cancel ? 0 : --this.running; + if ( this.running ) { + return; + } + + if ( this.options.clearStyle ) { + this.toShow.add( this.toHide ).css({ + height: "", + overflow: "" + }); + } + + // other classes are removed before the animation; this one needs to stay until completed + this.toHide.removeClass( "ui-accordion-content-active" ); + // Work around for rendering bug in IE (#5421) + if ( this.toHide.length ) { + this.toHide.parent()[0].className = this.toHide.parent()[0].className; + } + + this._trigger( "change", null, this.data ); + } +}); + +$.extend( $.ui.accordion, { + version: "1.8.11", + animations: { + slide: function( options, additions ) { + options = $.extend({ + easing: "swing", + duration: 300 + }, options, additions ); + if ( !options.toHide.size() ) { + options.toShow.animate({ + height: "show", + paddingTop: "show", + paddingBottom: "show" + }, options ); + return; + } + if ( !options.toShow.size() ) { + options.toHide.animate({ + height: "hide", + paddingTop: "hide", + paddingBottom: "hide" + }, options ); + return; + } + var overflow = options.toShow.css( "overflow" ), + percentDone = 0, + showProps = {}, + hideProps = {}, + fxAttrs = [ "height", "paddingTop", "paddingBottom" ], + originalWidth; + // fix width before calculating height of hidden element + var s = options.toShow; + originalWidth = s[0].style.width; + s.width( parseInt( s.parent().width(), 10 ) + - parseInt( s.css( "paddingLeft" ), 10 ) + - parseInt( s.css( "paddingRight" ), 10 ) + - ( parseInt( s.css( "borderLeftWidth" ), 10 ) || 0 ) + - ( parseInt( s.css( "borderRightWidth" ), 10) || 0 ) ); + + $.each( fxAttrs, function( i, prop ) { + hideProps[ prop ] = "hide"; + + var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ ); + showProps[ prop ] = { + value: parts[ 1 ], + unit: parts[ 2 ] || "px" + }; + }); + options.toShow.css({ height: 0, overflow: "hidden" }).show(); + options.toHide + .filter( ":hidden" ) + .each( options.complete ) + .end() + .filter( ":visible" ) + .animate( hideProps, { + step: function( now, settings ) { + // only calculate the percent when animating height + // IE gets very inconsistent results when animating elements + // with small values, which is common for padding + if ( settings.prop == "height" ) { + percentDone = ( settings.end - settings.start === 0 ) ? 0 : + ( settings.now - settings.start ) / ( settings.end - settings.start ); + } + + options.toShow[ 0 ].style[ settings.prop ] = + ( percentDone * showProps[ settings.prop ].value ) + + showProps[ settings.prop ].unit; + }, + duration: options.duration, + easing: options.easing, + complete: function() { + if ( !options.autoHeight ) { + options.toShow.css( "height", "" ); + } + options.toShow.css({ + width: originalWidth, + overflow: overflow + }); + options.complete(); + } + }); + }, + bounceslide: function( options ) { + this.slide( options, { + easing: options.down ? "easeOutBounce" : "swing", + duration: options.down ? 1000 : 200 + }); + } + } +}); + +})( jQuery ); +/* + * jQuery UI Autocomplete 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.position.js + */ +(function( $, undefined ) { + +// used to prevent race conditions with remote data sources +var requestIndex = 0; + +$.widget( "ui.autocomplete", { + options: { + appendTo: "body", + autoFocus: false, + delay: 300, + minLength: 1, + position: { + my: "left top", + at: "left bottom", + collision: "none" + }, + source: null + }, + + pending: 0, + + _create: function() { + var self = this, + doc = this.element[ 0 ].ownerDocument, + suppressKeyPress; + + this.element + .addClass( "ui-autocomplete-input" ) + .attr( "autocomplete", "off" ) + // TODO verify these actually work as intended + .attr({ + role: "textbox", + "aria-autocomplete": "list", + "aria-haspopup": "true" + }) + .bind( "keydown.autocomplete", function( event ) { + if ( self.options.disabled || self.element.attr( "readonly" ) ) { + return; + } + + suppressKeyPress = false; + var keyCode = $.ui.keyCode; + switch( event.keyCode ) { + case keyCode.PAGE_UP: + self._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + self._move( "nextPage", event ); + break; + case keyCode.UP: + self._move( "previous", event ); + // prevent moving cursor to beginning of text field in some browsers + event.preventDefault(); + break; + case keyCode.DOWN: + self._move( "next", event ); + // prevent moving cursor to end of text field in some browsers + event.preventDefault(); + break; + case keyCode.ENTER: + case keyCode.NUMPAD_ENTER: + // when menu is open and has focus + if ( self.menu.active ) { + // #6055 - Opera still allows the keypress to occur + // which causes forms to submit + suppressKeyPress = true; + event.preventDefault(); + } + //passthrough - ENTER and TAB both select the current element + case keyCode.TAB: + if ( !self.menu.active ) { + return; + } + self.menu.select( event ); + break; + case keyCode.ESCAPE: + self.element.val( self.term ); + self.close( event ); + break; + default: + // keypress is triggered before the input value is changed + clearTimeout( self.searching ); + self.searching = setTimeout(function() { + // only search if the value has changed + if ( self.term != self.element.val() ) { + self.selectedItem = null; + self.search( null, event ); + } + }, self.options.delay ); + break; + } + }) + .bind( "keypress.autocomplete", function( event ) { + if ( suppressKeyPress ) { + suppressKeyPress = false; + event.preventDefault(); + } + }) + .bind( "focus.autocomplete", function() { + if ( self.options.disabled ) { + return; + } + + self.selectedItem = null; + self.previous = self.element.val(); + }) + .bind( "blur.autocomplete", function( event ) { + if ( self.options.disabled ) { + return; + } + + clearTimeout( self.searching ); + // clicks on the menu (or a button to trigger a search) will cause a blur event + self.closing = setTimeout(function() { + self.close( event ); + self._change( event ); + }, 150 ); + }); + this._initSource(); + this.response = function() { + return self._response.apply( self, arguments ); + }; + this.menu = $( "
      " ) + .addClass( "ui-autocomplete" ) + .appendTo( $( this.options.appendTo || "body", doc )[0] ) + // prevent the close-on-blur in case of a "slow" click on the menu (long mousedown) + .mousedown(function( event ) { + // clicking on the scrollbar causes focus to shift to the body + // but we can't detect a mouseup or a click immediately afterward + // so we have to track the next mousedown and close the menu if + // the user clicks somewhere outside of the autocomplete + var menuElement = self.menu.element[ 0 ]; + if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { + setTimeout(function() { + $( document ).one( 'mousedown', function( event ) { + if ( event.target !== self.element[ 0 ] && + event.target !== menuElement && + !$.ui.contains( menuElement, event.target ) ) { + self.close(); + } + }); + }, 1 ); + } + + // use another timeout to make sure the blur-event-handler on the input was already triggered + setTimeout(function() { + clearTimeout( self.closing ); + }, 13); + }) + .menu({ + focus: function( event, ui ) { + var item = ui.item.data( "item.autocomplete" ); + if ( false !== self._trigger( "focus", event, { item: item } ) ) { + // use value to match what will end up in the input, if it was a key event + if ( /^key/.test(event.originalEvent.type) ) { + self.element.val( item.value ); + } + } + }, + selected: function( event, ui ) { + var item = ui.item.data( "item.autocomplete" ), + previous = self.previous; + + // only trigger when focus was lost (click on menu) + if ( self.element[0] !== doc.activeElement ) { + self.element.focus(); + self.previous = previous; + // #6109 - IE triggers two focus events and the second + // is asynchronous, so we need to reset the previous + // term synchronously and asynchronously :-( + setTimeout(function() { + self.previous = previous; + self.selectedItem = item; + }, 1); + } + + if ( false !== self._trigger( "select", event, { item: item } ) ) { + self.element.val( item.value ); + } + // reset the term after the select event + // this allows custom select handling to work properly + self.term = self.element.val(); + + self.close( event ); + self.selectedItem = item; + }, + blur: function( event, ui ) { + // don't set the value of the text field if it's already correct + // this prevents moving the cursor unnecessarily + if ( self.menu.element.is(":visible") && + ( self.element.val() !== self.term ) ) { + self.element.val( self.term ); + } + } + }) + .zIndex( this.element.zIndex() + 1 ) + // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781 + .css({ top: 0, left: 0 }) + .hide() + .data( "menu" ); + if ( $.fn.bgiframe ) { + this.menu.element.bgiframe(); + } + }, + + destroy: function() { + this.element + .removeClass( "ui-autocomplete-input" ) + .removeAttr( "autocomplete" ) + .removeAttr( "role" ) + .removeAttr( "aria-autocomplete" ) + .removeAttr( "aria-haspopup" ); + this.menu.element.remove(); + $.Widget.prototype.destroy.call( this ); + }, + + _setOption: function( key, value ) { + $.Widget.prototype._setOption.apply( this, arguments ); + if ( key === "source" ) { + this._initSource(); + } + if ( key === "appendTo" ) { + this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] ) + } + if ( key === "disabled" && value && this.xhr ) { + this.xhr.abort(); + } + }, + + _initSource: function() { + var self = this, + array, + url; + if ( $.isArray(this.options.source) ) { + array = this.options.source; + this.source = function( request, response ) { + response( $.ui.autocomplete.filter(array, request.term) ); + }; + } else if ( typeof this.options.source === "string" ) { + url = this.options.source; + this.source = function( request, response ) { + if ( self.xhr ) { + self.xhr.abort(); + } + self.xhr = $.ajax({ + url: url, + data: request, + dataType: "json", + autocompleteRequest: ++requestIndex, + success: function( data, status ) { + if ( this.autocompleteRequest === requestIndex ) { + response( data ); + } + }, + error: function() { + if ( this.autocompleteRequest === requestIndex ) { + response( [] ); + } + } + }); + }; + } else { + this.source = this.options.source; + } + }, + + search: function( value, event ) { + value = value != null ? value : this.element.val(); + + // always save the actual value, not the one passed as an argument + this.term = this.element.val(); + + if ( value.length < this.options.minLength ) { + return this.close( event ); + } + + clearTimeout( this.closing ); + if ( this._trigger( "search", event ) === false ) { + return; + } + + return this._search( value ); + }, + + _search: function( value ) { + this.pending++; + this.element.addClass( "ui-autocomplete-loading" ); + + this.source( { term: value }, this.response ); + }, + + _response: function( content ) { + if ( !this.options.disabled && content && content.length ) { + content = this._normalize( content ); + this._suggest( content ); + this._trigger( "open" ); + } else { + this.close(); + } + this.pending--; + if ( !this.pending ) { + this.element.removeClass( "ui-autocomplete-loading" ); + } + }, + + close: function( event ) { + clearTimeout( this.closing ); + if ( this.menu.element.is(":visible") ) { + this.menu.element.hide(); + this.menu.deactivate(); + this._trigger( "close", event ); + } + }, + + _change: function( event ) { + if ( this.previous !== this.element.val() ) { + this._trigger( "change", event, { item: this.selectedItem } ); + } + }, + + _normalize: function( items ) { + // assume all items have the right format when the first item is complete + if ( items.length && items[0].label && items[0].value ) { + return items; + } + return $.map( items, function(item) { + if ( typeof item === "string" ) { + return { + label: item, + value: item + }; + } + return $.extend({ + label: item.label || item.value, + value: item.value || item.label + }, item ); + }); + }, + + _suggest: function( items ) { + var ul = this.menu.element + .empty() + .zIndex( this.element.zIndex() + 1 ); + this._renderMenu( ul, items ); + // TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate + this.menu.deactivate(); + this.menu.refresh(); + + // size and position menu + ul.show(); + this._resizeMenu(); + ul.position( $.extend({ + of: this.element + }, this.options.position )); + + if ( this.options.autoFocus ) { + this.menu.next( new $.Event("mouseover") ); + } + }, + + _resizeMenu: function() { + var ul = this.menu.element; + ul.outerWidth( Math.max( + ul.width( "" ).outerWidth(), + this.element.outerWidth() + ) ); + }, + + _renderMenu: function( ul, items ) { + var self = this; + $.each( items, function( index, item ) { + self._renderItem( ul, item ); + }); + }, + + _renderItem: function( ul, item) { + return $( "
    • " ) + .data( "item.autocomplete", item ) + .append( $( "
      " ).text( item.label ) ) + .appendTo( ul ); + }, + + _move: function( direction, event ) { + if ( !this.menu.element.is(":visible") ) { + this.search( null, event ); + return; + } + if ( this.menu.first() && /^previous/.test(direction) || + this.menu.last() && /^next/.test(direction) ) { + this.element.val( this.term ); + this.menu.deactivate(); + return; + } + this.menu[ direction ]( event ); + }, + + widget: function() { + return this.menu.element; + } +}); + +$.extend( $.ui.autocomplete, { + escapeRegex: function( value ) { + return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + }, + filter: function(array, term) { + var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); + return $.grep( array, function(value) { + return matcher.test( value.label || value.value || value ); + }); + } +}); + +}( jQuery )); + +/* + * jQuery UI Menu (not officially released) + * + * This widget isn't yet finished and the API is subject to change. We plan to finish + * it for the next release. You're welcome to give it a try anyway and give us feedback, + * as long as you're okay with migrating your code later on. We can help with that, too. + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menu + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function($) { + +$.widget("ui.menu", { + _create: function() { + var self = this; + this.element + .addClass("ui-menu ui-widget ui-widget-content ui-corner-all") + .attr({ + role: "listbox", + "aria-activedescendant": "ui-active-menuitem" + }) + .click(function( event ) { + if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) { + return; + } + // temporary + event.preventDefault(); + self.select( event ); + }); + this.refresh(); + }, + + refresh: function() { + var self = this; + + // don't refresh list items that are already adapted + var items = this.element.children("li:not(.ui-menu-item):has(a)") + .addClass("ui-menu-item") + .attr("role", "menuitem"); + + items.children("a") + .addClass("ui-corner-all") + .attr("tabindex", -1) + // mouseenter doesn't work with event delegation + .mouseenter(function( event ) { + self.activate( event, $(this).parent() ); + }) + .mouseleave(function() { + self.deactivate(); + }); + }, + + activate: function( event, item ) { + this.deactivate(); + if (this.hasScroll()) { + var offset = item.offset().top - this.element.offset().top, + scroll = this.element.attr("scrollTop"), + elementHeight = this.element.height(); + if (offset < 0) { + this.element.attr("scrollTop", scroll + offset); + } else if (offset >= elementHeight) { + this.element.attr("scrollTop", scroll + offset - elementHeight + item.height()); + } + } + this.active = item.eq(0) + .children("a") + .addClass("ui-state-hover") + .attr("id", "ui-active-menuitem") + .end(); + this._trigger("focus", event, { item: item }); + }, + + deactivate: function() { + if (!this.active) { return; } + + this.active.children("a") + .removeClass("ui-state-hover") + .removeAttr("id"); + this._trigger("blur"); + this.active = null; + }, + + next: function(event) { + this.move("next", ".ui-menu-item:first", event); + }, + + previous: function(event) { + this.move("prev", ".ui-menu-item:last", event); + }, + + first: function() { + return this.active && !this.active.prevAll(".ui-menu-item").length; + }, + + last: function() { + return this.active && !this.active.nextAll(".ui-menu-item").length; + }, + + move: function(direction, edge, event) { + if (!this.active) { + this.activate(event, this.element.children(edge)); + return; + } + var next = this.active[direction + "All"](".ui-menu-item").eq(0); + if (next.length) { + this.activate(event, next); + } else { + this.activate(event, this.element.children(edge)); + } + }, + + // TODO merge with previousPage + nextPage: function(event) { + if (this.hasScroll()) { + // TODO merge with no-scroll-else + if (!this.active || this.last()) { + this.activate(event, this.element.children(".ui-menu-item:first")); + return; + } + var base = this.active.offset().top, + height = this.element.height(), + result = this.element.children(".ui-menu-item").filter(function() { + var close = $(this).offset().top - base - height + $(this).height(); + // TODO improve approximation + return close < 10 && close > -10; + }); + + // TODO try to catch this earlier when scrollTop indicates the last page anyway + if (!result.length) { + result = this.element.children(".ui-menu-item:last"); + } + this.activate(event, result); + } else { + this.activate(event, this.element.children(".ui-menu-item") + .filter(!this.active || this.last() ? ":first" : ":last")); + } + }, + + // TODO merge with nextPage + previousPage: function(event) { + if (this.hasScroll()) { + // TODO merge with no-scroll-else + if (!this.active || this.first()) { + this.activate(event, this.element.children(".ui-menu-item:last")); + return; + } + + var base = this.active.offset().top, + height = this.element.height(); + result = this.element.children(".ui-menu-item").filter(function() { + var close = $(this).offset().top - base + height - $(this).height(); + // TODO improve approximation + return close < 10 && close > -10; + }); + + // TODO try to catch this earlier when scrollTop indicates the last page anyway + if (!result.length) { + result = this.element.children(".ui-menu-item:first"); + } + this.activate(event, result); + } else { + this.activate(event, this.element.children(".ui-menu-item") + .filter(!this.active || this.first() ? ":last" : ":first")); + } + }, + + hasScroll: function() { + return this.element.height() < this.element.attr("scrollHeight"); + }, + + select: function( event ) { + this._trigger("selected", event, { item: this.active }); + } +}); + +}(jQuery)); +/* + * jQuery UI Button 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Button + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +var lastActive, + baseClasses = "ui-button ui-widget ui-state-default ui-corner-all", + stateClasses = "ui-state-hover ui-state-active ", + typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only", + formResetHandler = function( event ) { + $( ":ui-button", event.target.form ).each(function() { + var inst = $( this ).data( "button" ); + setTimeout(function() { + inst.refresh(); + }, 1 ); + }); + }, + radioGroup = function( radio ) { + var name = radio.name, + form = radio.form, + radios = $( [] ); + if ( name ) { + if ( form ) { + radios = $( form ).find( "[name='" + name + "']" ); + } else { + radios = $( "[name='" + name + "']", radio.ownerDocument ) + .filter(function() { + return !this.form; + }); + } + } + return radios; + }; + +$.widget( "ui.button", { + options: { + disabled: null, + text: true, + label: null, + icons: { + primary: null, + secondary: null + } + }, + _create: function() { + this.element.closest( "form" ) + .unbind( "reset.button" ) + .bind( "reset.button", formResetHandler ); + + if ( typeof this.options.disabled !== "boolean" ) { + this.options.disabled = this.element.attr( "disabled" ); + } + + this._determineButtonType(); + this.hasTitle = !!this.buttonElement.attr( "title" ); + + var self = this, + options = this.options, + toggleButton = this.type === "checkbox" || this.type === "radio", + hoverClass = "ui-state-hover" + ( !toggleButton ? " ui-state-active" : "" ), + focusClass = "ui-state-focus"; + + if ( options.label === null ) { + options.label = this.buttonElement.html(); + } + + if ( this.element.is( ":disabled" ) ) { + options.disabled = true; + } + + this.buttonElement + .addClass( baseClasses ) + .attr( "role", "button" ) + .bind( "mouseenter.button", function() { + if ( options.disabled ) { + return; + } + $( this ).addClass( "ui-state-hover" ); + if ( this === lastActive ) { + $( this ).addClass( "ui-state-active" ); + } + }) + .bind( "mouseleave.button", function() { + if ( options.disabled ) { + return; + } + $( this ).removeClass( hoverClass ); + }) + .bind( "focus.button", function() { + // no need to check disabled, focus won't be triggered anyway + $( this ).addClass( focusClass ); + }) + .bind( "blur.button", function() { + $( this ).removeClass( focusClass ); + }); + + if ( toggleButton ) { + this.element.bind( "change.button", function() { + self.refresh(); + }); + } + + if ( this.type === "checkbox" ) { + this.buttonElement.bind( "click.button", function() { + if ( options.disabled ) { + return false; + } + $( this ).toggleClass( "ui-state-active" ); + self.buttonElement.attr( "aria-pressed", self.element[0].checked ); + }); + } else if ( this.type === "radio" ) { + this.buttonElement.bind( "click.button", function() { + if ( options.disabled ) { + return false; + } + $( this ).addClass( "ui-state-active" ); + self.buttonElement.attr( "aria-pressed", true ); + + var radio = self.element[ 0 ]; + radioGroup( radio ) + .not( radio ) + .map(function() { + return $( this ).button( "widget" )[ 0 ]; + }) + .removeClass( "ui-state-active" ) + .attr( "aria-pressed", false ); + }); + } else { + this.buttonElement + .bind( "mousedown.button", function() { + if ( options.disabled ) { + return false; + } + $( this ).addClass( "ui-state-active" ); + lastActive = this; + $( document ).one( "mouseup", function() { + lastActive = null; + }); + }) + .bind( "mouseup.button", function() { + if ( options.disabled ) { + return false; + } + $( this ).removeClass( "ui-state-active" ); + }) + .bind( "keydown.button", function(event) { + if ( options.disabled ) { + return false; + } + if ( event.keyCode == $.ui.keyCode.SPACE || event.keyCode == $.ui.keyCode.ENTER ) { + $( this ).addClass( "ui-state-active" ); + } + }) + .bind( "keyup.button", function() { + $( this ).removeClass( "ui-state-active" ); + }); + + if ( this.buttonElement.is("a") ) { + this.buttonElement.keyup(function(event) { + if ( event.keyCode === $.ui.keyCode.SPACE ) { + // TODO pass through original event correctly (just as 2nd argument doesn't work) + $( this ).click(); + } + }); + } + } + + // TODO: pull out $.Widget's handling for the disabled option into + // $.Widget.prototype._setOptionDisabled so it's easy to proxy and can + // be overridden by individual plugins + this._setOption( "disabled", options.disabled ); + }, + + _determineButtonType: function() { + + if ( this.element.is(":checkbox") ) { + this.type = "checkbox"; + } else { + if ( this.element.is(":radio") ) { + this.type = "radio"; + } else { + if ( this.element.is("input") ) { + this.type = "input"; + } else { + this.type = "button"; + } + } + } + + if ( this.type === "checkbox" || this.type === "radio" ) { + // we don't search against the document in case the element + // is disconnected from the DOM + var ancestor = this.element.parents().filter(":last"), + labelSelector = "label[for=" + this.element.attr("id") + "]"; + this.buttonElement = ancestor.find( labelSelector ); + if ( !this.buttonElement.length ) { + ancestor = ancestor.length ? ancestor.siblings() : this.element.siblings(); + this.buttonElement = ancestor.filter( labelSelector ); + if ( !this.buttonElement.length ) { + this.buttonElement = ancestor.find( labelSelector ); + } + } + this.element.addClass( "ui-helper-hidden-accessible" ); + + var checked = this.element.is( ":checked" ); + if ( checked ) { + this.buttonElement.addClass( "ui-state-active" ); + } + this.buttonElement.attr( "aria-pressed", checked ); + } else { + this.buttonElement = this.element; + } + }, + + widget: function() { + return this.buttonElement; + }, + + destroy: function() { + this.element + .removeClass( "ui-helper-hidden-accessible" ); + this.buttonElement + .removeClass( baseClasses + " " + stateClasses + " " + typeClasses ) + .removeAttr( "role" ) + .removeAttr( "aria-pressed" ) + .html( this.buttonElement.find(".ui-button-text").html() ); + + if ( !this.hasTitle ) { + this.buttonElement.removeAttr( "title" ); + } + + $.Widget.prototype.destroy.call( this ); + }, + + _setOption: function( key, value ) { + $.Widget.prototype._setOption.apply( this, arguments ); + if ( key === "disabled" ) { + if ( value ) { + this.element.attr( "disabled", true ); + } else { + this.element.removeAttr( "disabled" ); + } + } + this._resetButton(); + }, + + refresh: function() { + var isDisabled = this.element.is( ":disabled" ); + if ( isDisabled !== this.options.disabled ) { + this._setOption( "disabled", isDisabled ); + } + if ( this.type === "radio" ) { + radioGroup( this.element[0] ).each(function() { + if ( $( this ).is( ":checked" ) ) { + $( this ).button( "widget" ) + .addClass( "ui-state-active" ) + .attr( "aria-pressed", true ); + } else { + $( this ).button( "widget" ) + .removeClass( "ui-state-active" ) + .attr( "aria-pressed", false ); + } + }); + } else if ( this.type === "checkbox" ) { + if ( this.element.is( ":checked" ) ) { + this.buttonElement + .addClass( "ui-state-active" ) + .attr( "aria-pressed", true ); + } else { + this.buttonElement + .removeClass( "ui-state-active" ) + .attr( "aria-pressed", false ); + } + } + }, + + _resetButton: function() { + if ( this.type === "input" ) { + if ( this.options.label ) { + this.element.val( this.options.label ); + } + return; + } + var buttonElement = this.buttonElement.removeClass( typeClasses ), + buttonText = $( "" ) + .addClass( "ui-button-text" ) + .html( this.options.label ) + .appendTo( buttonElement.empty() ) + .text(), + icons = this.options.icons, + multipleIcons = icons.primary && icons.secondary, + buttonClasses = []; + + if ( icons.primary || icons.secondary ) { + if ( this.options.text ) { + buttonClasses.push( "ui-button-text-icon" + ( multipleIcons ? "s" : ( icons.primary ? "-primary" : "-secondary" ) ) ); + } + + if ( icons.primary ) { + buttonElement.prepend( "" ); + } + + if ( icons.secondary ) { + buttonElement.append( "" ); + } + + if ( !this.options.text ) { + buttonClasses.push( multipleIcons ? "ui-button-icons-only" : "ui-button-icon-only" ); + + if ( !this.hasTitle ) { + buttonElement.attr( "title", buttonText ); + } + } + } else { + buttonClasses.push( "ui-button-text-only" ); + } + buttonElement.addClass( buttonClasses.join( " " ) ); + } +}); + +$.widget( "ui.buttonset", { + options: { + items: ":button, :submit, :reset, :checkbox, :radio, a, :data(button)" + }, + + _create: function() { + this.element.addClass( "ui-buttonset" ); + }, + + _init: function() { + this.refresh(); + }, + + _setOption: function( key, value ) { + if ( key === "disabled" ) { + this.buttons.button( "option", key, value ); + } + + $.Widget.prototype._setOption.apply( this, arguments ); + }, + + refresh: function() { + this.buttons = this.element.find( this.options.items ) + .filter( ":ui-button" ) + .button( "refresh" ) + .end() + .not( ":ui-button" ) + .button() + .end() + .map(function() { + return $( this ).button( "widget" )[ 0 ]; + }) + .removeClass( "ui-corner-all ui-corner-left ui-corner-right" ) + .filter( ":first" ) + .addClass( "ui-corner-left" ) + .end() + .filter( ":last" ) + .addClass( "ui-corner-right" ) + .end() + .end(); + }, + + destroy: function() { + this.element.removeClass( "ui-buttonset" ); + this.buttons + .map(function() { + return $( this ).button( "widget" )[ 0 ]; + }) + .removeClass( "ui-corner-left ui-corner-right" ) + .end() + .button( "destroy" ); + + $.Widget.prototype.destroy.call( this ); + } +}); + +}( jQuery ) ); +/* + * jQuery UI Datepicker 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Datepicker + * + * Depends: + * jquery.ui.core.js + */ +(function( $, undefined ) { + +$.extend($.ui, { datepicker: { version: "1.8.11" } }); + +var PROP_NAME = 'datepicker'; +var dpuuid = new Date().getTime(); + +/* Date picker manager. + Use the singleton instance of this class, $.datepicker, to interact with the date picker. + Settings for (groups of) date pickers are maintained in an instance object, + allowing multiple different settings on the same page. */ + +function Datepicker() { + this.debug = false; // Change this to true to start debugging + this._curInst = null; // The current instance in use + this._keyEvent = false; // If the last event was a key event + this._disabledInputs = []; // List of date picker inputs that have been disabled + this._datepickerShowing = false; // True if the popup picker is showing , false if not + this._inDialog = false; // True if showing within a "dialog", false if not + this._mainDivId = 'ui-datepicker-div'; // The ID of the main datepicker division + this._inlineClass = 'ui-datepicker-inline'; // The name of the inline marker class + this._appendClass = 'ui-datepicker-append'; // The name of the append marker class + this._triggerClass = 'ui-datepicker-trigger'; // The name of the trigger marker class + this._dialogClass = 'ui-datepicker-dialog'; // The name of the dialog marker class + this._disableClass = 'ui-datepicker-disabled'; // The name of the disabled covering marker class + this._unselectableClass = 'ui-datepicker-unselectable'; // The name of the unselectable cell marker class + this._currentClass = 'ui-datepicker-current-day'; // The name of the current day marker class + this._dayOverClass = 'ui-datepicker-days-cell-over'; // The name of the day hover marker class + this.regional = []; // Available regional settings, indexed by language code + this.regional[''] = { // Default regional settings + closeText: 'Done', // Display text for close link + prevText: 'Prev', // Display text for previous month link + nextText: 'Next', // Display text for next month link + currentText: 'Today', // Display text for current month link + monthNames: ['January','February','March','April','May','June', + 'July','August','September','October','November','December'], // Names of months for drop-down and formatting + monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], // For formatting + dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // For formatting + dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], // For formatting + dayNamesMin: ['Su','Mo','Tu','We','Th','Fr','Sa'], // Column headings for days starting at Sunday + weekHeader: 'Wk', // Column header for week of the year + dateFormat: 'mm/dd/yy', // See format options on parseDate + firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... + isRTL: false, // True if right-to-left language, false if left-to-right + showMonthAfterYear: false, // True if the year select precedes month, false for month then year + yearSuffix: '' // Additional text to append to the year in the month headers + }; + this._defaults = { // Global defaults for all the date picker instances + showOn: 'focus', // 'focus' for popup on focus, + // 'button' for trigger button, or 'both' for either + showAnim: 'fadeIn', // Name of jQuery animation for popup + showOptions: {}, // Options for enhanced animations + defaultDate: null, // Used when field is blank: actual date, + // +/-number for offset from today, null for today + appendText: '', // Display text following the input box, e.g. showing the format + buttonText: '...', // Text for trigger button + buttonImage: '', // URL for trigger button image + buttonImageOnly: false, // True if the image appears alone, false if it appears on a button + hideIfNoPrevNext: false, // True to hide next/previous month links + // if not applicable, false to just disable them + navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links + gotoCurrent: false, // True if today link goes back to current selection instead + changeMonth: false, // True if month can be selected directly, false if only prev/next + changeYear: false, // True if year can be selected directly, false if only prev/next + yearRange: 'c-10:c+10', // Range of years to display in drop-down, + // either relative to today's year (-nn:+nn), relative to currently displayed year + // (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n) + showOtherMonths: false, // True to show dates in other months, false to leave blank + selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable + showWeek: false, // True to show week of the year, false to not show it + calculateWeek: this.iso8601Week, // How to calculate the week of the year, + // takes a Date and returns the number of the week for it + shortYearCutoff: '+10', // Short year values < this are in the current century, + // > this are in the previous century, + // string value starting with '+' for current year + value + minDate: null, // The earliest selectable date, or null for no limit + maxDate: null, // The latest selectable date, or null for no limit + duration: 'fast', // Duration of display/closure + beforeShowDay: null, // Function that takes a date and returns an array with + // [0] = true if selectable, false if not, [1] = custom CSS class name(s) or '', + // [2] = cell title (optional), e.g. $.datepicker.noWeekends + beforeShow: null, // Function that takes an input field and + // returns a set of custom settings for the date picker + onSelect: null, // Define a callback function when a date is selected + onChangeMonthYear: null, // Define a callback function when the month or year is changed + onClose: null, // Define a callback function when the datepicker is closed + numberOfMonths: 1, // Number of months to show at a time + showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0) + stepMonths: 1, // Number of months to step back/forward + stepBigMonths: 12, // Number of months to step back/forward for the big links + altField: '', // Selector for an alternate field to store selected dates into + altFormat: '', // The date format to use for the alternate field + constrainInput: true, // The input is constrained by the current date format + showButtonPanel: false, // True to show button panel, false to not show it + autoSize: false // True to size the input for the date format, false to leave as is + }; + $.extend(this._defaults, this.regional['']); + this.dpDiv = $('
      '); +} + +$.extend(Datepicker.prototype, { + /* Class name added to elements to indicate already configured with a date picker. */ + markerClassName: 'hasDatepicker', + + /* Debug logging (if enabled). */ + log: function () { + if (this.debug) + console.log.apply('', arguments); + }, + + // TODO rename to "widget" when switching to widget factory + _widgetDatepicker: function() { + return this.dpDiv; + }, + + /* Override the default settings for all instances of the date picker. + @param settings object - the new settings to use as defaults (anonymous object) + @return the manager object */ + setDefaults: function(settings) { + extendRemove(this._defaults, settings || {}); + return this; + }, + + /* Attach the date picker to a jQuery selection. + @param target element - the target input field or division or span + @param settings object - the new settings to use for this date picker instance (anonymous) */ + _attachDatepicker: function(target, settings) { + // check for settings on the control itself - in namespace 'date:' + var inlineSettings = null; + for (var attrName in this._defaults) { + var attrValue = target.getAttribute('date:' + attrName); + if (attrValue) { + inlineSettings = inlineSettings || {}; + try { + inlineSettings[attrName] = eval(attrValue); + } catch (err) { + inlineSettings[attrName] = attrValue; + } + } + } + var nodeName = target.nodeName.toLowerCase(); + var inline = (nodeName == 'div' || nodeName == 'span'); + if (!target.id) { + this.uuid += 1; + target.id = 'dp' + this.uuid; + } + var inst = this._newInst($(target), inline); + inst.settings = $.extend({}, settings || {}, inlineSettings || {}); + if (nodeName == 'input') { + this._connectDatepicker(target, inst); + } else if (inline) { + this._inlineDatepicker(target, inst); + } + }, + + /* Create a new instance object. */ + _newInst: function(target, inline) { + var id = target[0].id.replace(/([^A-Za-z0-9_-])/g, '\\\\$1'); // escape jQuery meta chars + return {id: id, input: target, // associated target + selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection + drawMonth: 0, drawYear: 0, // month being drawn + inline: inline, // is datepicker inline or not + dpDiv: (!inline ? this.dpDiv : // presentation div + $('
      '))}; + }, + + /* Attach the date picker to an input field. */ + _connectDatepicker: function(target, inst) { + var input = $(target); + inst.append = $([]); + inst.trigger = $([]); + if (input.hasClass(this.markerClassName)) + return; + this._attachments(input, inst); + input.addClass(this.markerClassName).keydown(this._doKeyDown). + keypress(this._doKeyPress).keyup(this._doKeyUp). + bind("setData.datepicker", function(event, key, value) { + inst.settings[key] = value; + }).bind("getData.datepicker", function(event, key) { + return this._get(inst, key); + }); + this._autoSize(inst); + $.data(target, PROP_NAME, inst); + }, + + /* Make attachments based on settings. */ + _attachments: function(input, inst) { + var appendText = this._get(inst, 'appendText'); + var isRTL = this._get(inst, 'isRTL'); + if (inst.append) + inst.append.remove(); + if (appendText) { + inst.append = $('' + appendText + ''); + input[isRTL ? 'before' : 'after'](inst.append); + } + input.unbind('focus', this._showDatepicker); + if (inst.trigger) + inst.trigger.remove(); + var showOn = this._get(inst, 'showOn'); + if (showOn == 'focus' || showOn == 'both') // pop-up date picker when in the marked field + input.focus(this._showDatepicker); + if (showOn == 'button' || showOn == 'both') { // pop-up date picker when button clicked + var buttonText = this._get(inst, 'buttonText'); + var buttonImage = this._get(inst, 'buttonImage'); + inst.trigger = $(this._get(inst, 'buttonImageOnly') ? + $('').addClass(this._triggerClass). + attr({ src: buttonImage, alt: buttonText, title: buttonText }) : + $('').addClass(this._triggerClass). + html(buttonImage == '' ? buttonText : $('').attr( + { src:buttonImage, alt:buttonText, title:buttonText }))); + input[isRTL ? 'before' : 'after'](inst.trigger); + inst.trigger.click(function() { + if ($.datepicker._datepickerShowing && $.datepicker._lastInput == input[0]) + $.datepicker._hideDatepicker(); + else + $.datepicker._showDatepicker(input[0]); + return false; + }); + } + }, + + /* Apply the maximum length for the date format. */ + _autoSize: function(inst) { + if (this._get(inst, 'autoSize') && !inst.inline) { + var date = new Date(2009, 12 - 1, 20); // Ensure double digits + var dateFormat = this._get(inst, 'dateFormat'); + if (dateFormat.match(/[DM]/)) { + var findMax = function(names) { + var max = 0; + var maxI = 0; + for (var i = 0; i < names.length; i++) { + if (names[i].length > max) { + max = names[i].length; + maxI = i; + } + } + return maxI; + }; + date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ? + 'monthNames' : 'monthNamesShort')))); + date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ? + 'dayNames' : 'dayNamesShort'))) + 20 - date.getDay()); + } + inst.input.attr('size', this._formatDate(inst, date).length); + } + }, + + /* Attach an inline date picker to a div. */ + _inlineDatepicker: function(target, inst) { + var divSpan = $(target); + if (divSpan.hasClass(this.markerClassName)) + return; + divSpan.addClass(this.markerClassName).append(inst.dpDiv). + bind("setData.datepicker", function(event, key, value){ + inst.settings[key] = value; + }).bind("getData.datepicker", function(event, key){ + return this._get(inst, key); + }); + $.data(target, PROP_NAME, inst); + this._setDate(inst, this._getDefaultDate(inst), true); + this._updateDatepicker(inst); + this._updateAlternate(inst); + inst.dpDiv.show(); + }, + + /* Pop-up the date picker in a "dialog" box. + @param input element - ignored + @param date string or Date - the initial date to display + @param onSelect function - the function to call when a date is selected + @param settings object - update the dialog date picker instance's settings (anonymous object) + @param pos int[2] - coordinates for the dialog's position within the screen or + event - with x/y coordinates or + leave empty for default (screen centre) + @return the manager object */ + _dialogDatepicker: function(input, date, onSelect, settings, pos) { + var inst = this._dialogInst; // internal instance + if (!inst) { + this.uuid += 1; + var id = 'dp' + this.uuid; + this._dialogInput = $(''); + this._dialogInput.keydown(this._doKeyDown); + $('body').append(this._dialogInput); + inst = this._dialogInst = this._newInst(this._dialogInput, false); + inst.settings = {}; + $.data(this._dialogInput[0], PROP_NAME, inst); + } + extendRemove(inst.settings, settings || {}); + date = (date && date.constructor == Date ? this._formatDate(inst, date) : date); + this._dialogInput.val(date); + + this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null); + if (!this._pos) { + var browserWidth = document.documentElement.clientWidth; + var browserHeight = document.documentElement.clientHeight; + var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; + var scrollY = document.documentElement.scrollTop || document.body.scrollTop; + this._pos = // should use actual width/height below + [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY]; + } + + // move input on screen for focus, but hidden behind dialog + this._dialogInput.css('left', (this._pos[0] + 20) + 'px').css('top', this._pos[1] + 'px'); + inst.settings.onSelect = onSelect; + this._inDialog = true; + this.dpDiv.addClass(this._dialogClass); + this._showDatepicker(this._dialogInput[0]); + if ($.blockUI) + $.blockUI(this.dpDiv); + $.data(this._dialogInput[0], PROP_NAME, inst); + return this; + }, + + /* Detach a datepicker from its control. + @param target element - the target input field or division or span */ + _destroyDatepicker: function(target) { + var $target = $(target); + var inst = $.data(target, PROP_NAME); + if (!$target.hasClass(this.markerClassName)) { + return; + } + var nodeName = target.nodeName.toLowerCase(); + $.removeData(target, PROP_NAME); + if (nodeName == 'input') { + inst.append.remove(); + inst.trigger.remove(); + $target.removeClass(this.markerClassName). + unbind('focus', this._showDatepicker). + unbind('keydown', this._doKeyDown). + unbind('keypress', this._doKeyPress). + unbind('keyup', this._doKeyUp); + } else if (nodeName == 'div' || nodeName == 'span') + $target.removeClass(this.markerClassName).empty(); + }, + + /* Enable the date picker to a jQuery selection. + @param target element - the target input field or division or span */ + _enableDatepicker: function(target) { + var $target = $(target); + var inst = $.data(target, PROP_NAME); + if (!$target.hasClass(this.markerClassName)) { + return; + } + var nodeName = target.nodeName.toLowerCase(); + if (nodeName == 'input') { + target.disabled = false; + inst.trigger.filter('button'). + each(function() { this.disabled = false; }).end(). + filter('img').css({opacity: '1.0', cursor: ''}); + } + else if (nodeName == 'div' || nodeName == 'span') { + var inline = $target.children('.' + this._inlineClass); + inline.children().removeClass('ui-state-disabled'); + } + this._disabledInputs = $.map(this._disabledInputs, + function(value) { return (value == target ? null : value); }); // delete entry + }, + + /* Disable the date picker to a jQuery selection. + @param target element - the target input field or division or span */ + _disableDatepicker: function(target) { + var $target = $(target); + var inst = $.data(target, PROP_NAME); + if (!$target.hasClass(this.markerClassName)) { + return; + } + var nodeName = target.nodeName.toLowerCase(); + if (nodeName == 'input') { + target.disabled = true; + inst.trigger.filter('button'). + each(function() { this.disabled = true; }).end(). + filter('img').css({opacity: '0.5', cursor: 'default'}); + } + else if (nodeName == 'div' || nodeName == 'span') { + var inline = $target.children('.' + this._inlineClass); + inline.children().addClass('ui-state-disabled'); + } + this._disabledInputs = $.map(this._disabledInputs, + function(value) { return (value == target ? null : value); }); // delete entry + this._disabledInputs[this._disabledInputs.length] = target; + }, + + /* Is the first field in a jQuery collection disabled as a datepicker? + @param target element - the target input field or division or span + @return boolean - true if disabled, false if enabled */ + _isDisabledDatepicker: function(target) { + if (!target) { + return false; + } + for (var i = 0; i < this._disabledInputs.length; i++) { + if (this._disabledInputs[i] == target) + return true; + } + return false; + }, + + /* Retrieve the instance data for the target control. + @param target element - the target input field or division or span + @return object - the associated instance data + @throws error if a jQuery problem getting data */ + _getInst: function(target) { + try { + return $.data(target, PROP_NAME); + } + catch (err) { + throw 'Missing instance data for this datepicker'; + } + }, + + /* Update or retrieve the settings for a date picker attached to an input field or division. + @param target element - the target input field or division or span + @param name object - the new settings to update or + string - the name of the setting to change or retrieve, + when retrieving also 'all' for all instance settings or + 'defaults' for all global defaults + @param value any - the new value for the setting + (omit if above is an object or to retrieve a value) */ + _optionDatepicker: function(target, name, value) { + var inst = this._getInst(target); + if (arguments.length == 2 && typeof name == 'string') { + return (name == 'defaults' ? $.extend({}, $.datepicker._defaults) : + (inst ? (name == 'all' ? $.extend({}, inst.settings) : + this._get(inst, name)) : null)); + } + var settings = name || {}; + if (typeof name == 'string') { + settings = {}; + settings[name] = value; + } + if (inst) { + if (this._curInst == inst) { + this._hideDatepicker(); + } + var date = this._getDateDatepicker(target, true); + var minDate = this._getMinMaxDate(inst, 'min'); + var maxDate = this._getMinMaxDate(inst, 'max'); + extendRemove(inst.settings, settings); + // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided + if (minDate !== null && settings['dateFormat'] !== undefined && settings['minDate'] === undefined) + inst.settings.minDate = this._formatDate(inst, minDate); + if (maxDate !== null && settings['dateFormat'] !== undefined && settings['maxDate'] === undefined) + inst.settings.maxDate = this._formatDate(inst, maxDate); + this._attachments($(target), inst); + this._autoSize(inst); + this._setDateDatepicker(target, date); + this._updateDatepicker(inst); + } + }, + + // change method deprecated + _changeDatepicker: function(target, name, value) { + this._optionDatepicker(target, name, value); + }, + + /* Redraw the date picker attached to an input field or division. + @param target element - the target input field or division or span */ + _refreshDatepicker: function(target) { + var inst = this._getInst(target); + if (inst) { + this._updateDatepicker(inst); + } + }, + + /* Set the dates for a jQuery selection. + @param target element - the target input field or division or span + @param date Date - the new date */ + _setDateDatepicker: function(target, date) { + var inst = this._getInst(target); + if (inst) { + this._setDate(inst, date); + this._updateDatepicker(inst); + this._updateAlternate(inst); + } + }, + + /* Get the date(s) for the first entry in a jQuery selection. + @param target element - the target input field or division or span + @param noDefault boolean - true if no default date is to be used + @return Date - the current date */ + _getDateDatepicker: function(target, noDefault) { + var inst = this._getInst(target); + if (inst && !inst.inline) + this._setDateFromField(inst, noDefault); + return (inst ? this._getDate(inst) : null); + }, + + /* Handle keystrokes. */ + _doKeyDown: function(event) { + var inst = $.datepicker._getInst(event.target); + var handled = true; + var isRTL = inst.dpDiv.is('.ui-datepicker-rtl'); + inst._keyEvent = true; + if ($.datepicker._datepickerShowing) + switch (event.keyCode) { + case 9: $.datepicker._hideDatepicker(); + handled = false; + break; // hide on tab out + case 13: var sel = $('td.' + $.datepicker._dayOverClass + ':not(.' + + $.datepicker._currentClass + ')', inst.dpDiv); + if (sel[0]) + $.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]); + else + $.datepicker._hideDatepicker(); + return false; // don't submit the form + break; // select the value on enter + case 27: $.datepicker._hideDatepicker(); + break; // hide on escape + case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ? + -$.datepicker._get(inst, 'stepBigMonths') : + -$.datepicker._get(inst, 'stepMonths')), 'M'); + break; // previous month/year on page up/+ ctrl + case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ? + +$.datepicker._get(inst, 'stepBigMonths') : + +$.datepicker._get(inst, 'stepMonths')), 'M'); + break; // next month/year on page down/+ ctrl + case 35: if (event.ctrlKey || event.metaKey) $.datepicker._clearDate(event.target); + handled = event.ctrlKey || event.metaKey; + break; // clear on ctrl or command +end + case 36: if (event.ctrlKey || event.metaKey) $.datepicker._gotoToday(event.target); + handled = event.ctrlKey || event.metaKey; + break; // current on ctrl or command +home + case 37: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), 'D'); + handled = event.ctrlKey || event.metaKey; + // -1 day on ctrl or command +left + if (event.originalEvent.altKey) $.datepicker._adjustDate(event.target, (event.ctrlKey ? + -$.datepicker._get(inst, 'stepBigMonths') : + -$.datepicker._get(inst, 'stepMonths')), 'M'); + // next month/year on alt +left on Mac + break; + case 38: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, -7, 'D'); + handled = event.ctrlKey || event.metaKey; + break; // -1 week on ctrl or command +up + case 39: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), 'D'); + handled = event.ctrlKey || event.metaKey; + // +1 day on ctrl or command +right + if (event.originalEvent.altKey) $.datepicker._adjustDate(event.target, (event.ctrlKey ? + +$.datepicker._get(inst, 'stepBigMonths') : + +$.datepicker._get(inst, 'stepMonths')), 'M'); + // next month/year on alt +right + break; + case 40: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, +7, 'D'); + handled = event.ctrlKey || event.metaKey; + break; // +1 week on ctrl or command +down + default: handled = false; + } + else if (event.keyCode == 36 && event.ctrlKey) // display the date picker on ctrl+home + $.datepicker._showDatepicker(this); + else { + handled = false; + } + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + }, + + /* Filter entered characters - based on date format. */ + _doKeyPress: function(event) { + var inst = $.datepicker._getInst(event.target); + if ($.datepicker._get(inst, 'constrainInput')) { + var chars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')); + var chr = String.fromCharCode(event.charCode == undefined ? event.keyCode : event.charCode); + return event.ctrlKey || event.metaKey || (chr < ' ' || !chars || chars.indexOf(chr) > -1); + } + }, + + /* Synchronise manual entry and field/alternate field. */ + _doKeyUp: function(event) { + var inst = $.datepicker._getInst(event.target); + if (inst.input.val() != inst.lastVal) { + try { + var date = $.datepicker.parseDate($.datepicker._get(inst, 'dateFormat'), + (inst.input ? inst.input.val() : null), + $.datepicker._getFormatConfig(inst)); + if (date) { // only if valid + $.datepicker._setDateFromField(inst); + $.datepicker._updateAlternate(inst); + $.datepicker._updateDatepicker(inst); + } + } + catch (event) { + $.datepicker.log(event); + } + } + return true; + }, + + /* Pop-up the date picker for a given input field. + @param input element - the input field attached to the date picker or + event - if triggered by focus */ + _showDatepicker: function(input) { + input = input.target || input; + if (input.nodeName.toLowerCase() != 'input') // find from button/image trigger + input = $('input', input.parentNode)[0]; + if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput == input) // already here + return; + var inst = $.datepicker._getInst(input); + if ($.datepicker._curInst && $.datepicker._curInst != inst) { + $.datepicker._curInst.dpDiv.stop(true, true); + } + var beforeShow = $.datepicker._get(inst, 'beforeShow'); + extendRemove(inst.settings, (beforeShow ? beforeShow.apply(input, [input, inst]) : {})); + inst.lastVal = null; + $.datepicker._lastInput = input; + $.datepicker._setDateFromField(inst); + if ($.datepicker._inDialog) // hide cursor + input.value = ''; + if (!$.datepicker._pos) { // position below input + $.datepicker._pos = $.datepicker._findPos(input); + $.datepicker._pos[1] += input.offsetHeight; // add the height + } + var isFixed = false; + $(input).parents().each(function() { + isFixed |= $(this).css('position') == 'fixed'; + return !isFixed; + }); + if (isFixed && $.browser.opera) { // correction for Opera when fixed and scrolled + $.datepicker._pos[0] -= document.documentElement.scrollLeft; + $.datepicker._pos[1] -= document.documentElement.scrollTop; + } + var offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]}; + $.datepicker._pos = null; + //to avoid flashes on Firefox + inst.dpDiv.empty(); + // determine sizing offscreen + inst.dpDiv.css({position: 'absolute', display: 'block', top: '-1000px'}); + $.datepicker._updateDatepicker(inst); + // fix width for dynamic number of date pickers + // and adjust position before showing + offset = $.datepicker._checkOffset(inst, offset, isFixed); + inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ? + 'static' : (isFixed ? 'fixed' : 'absolute')), display: 'none', + left: offset.left + 'px', top: offset.top + 'px'}); + if (!inst.inline) { + var showAnim = $.datepicker._get(inst, 'showAnim'); + var duration = $.datepicker._get(inst, 'duration'); + var postProcess = function() { + $.datepicker._datepickerShowing = true; + var cover = inst.dpDiv.find('iframe.ui-datepicker-cover'); // IE6- only + if( !! cover.length ){ + var borders = $.datepicker._getBorders(inst.dpDiv); + cover.css({left: -borders[0], top: -borders[1], + width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()}); + } + }; + inst.dpDiv.zIndex($(input).zIndex()+1); + if ($.effects && $.effects[showAnim]) + inst.dpDiv.show(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess); + else + inst.dpDiv[showAnim || 'show']((showAnim ? duration : null), postProcess); + if (!showAnim || !duration) + postProcess(); + if (inst.input.is(':visible') && !inst.input.is(':disabled')) + inst.input.focus(); + $.datepicker._curInst = inst; + } + }, + + /* Generate the date picker content. */ + _updateDatepicker: function(inst) { + var self = this; + var borders = $.datepicker._getBorders(inst.dpDiv); + inst.dpDiv.empty().append(this._generateHTML(inst)); + var cover = inst.dpDiv.find('iframe.ui-datepicker-cover'); // IE6- only + if( !!cover.length ){ //avoid call to outerXXXX() when not in IE6 + cover.css({left: -borders[0], top: -borders[1], width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()}) + } + inst.dpDiv.find('button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a') + .bind('mouseout', function(){ + $(this).removeClass('ui-state-hover'); + if(this.className.indexOf('ui-datepicker-prev') != -1) $(this).removeClass('ui-datepicker-prev-hover'); + if(this.className.indexOf('ui-datepicker-next') != -1) $(this).removeClass('ui-datepicker-next-hover'); + }) + .bind('mouseover', function(){ + if (!self._isDisabledDatepicker( inst.inline ? inst.dpDiv.parent()[0] : inst.input[0])) { + $(this).parents('.ui-datepicker-calendar').find('a').removeClass('ui-state-hover'); + $(this).addClass('ui-state-hover'); + if(this.className.indexOf('ui-datepicker-prev') != -1) $(this).addClass('ui-datepicker-prev-hover'); + if(this.className.indexOf('ui-datepicker-next') != -1) $(this).addClass('ui-datepicker-next-hover'); + } + }) + .end() + .find('.' + this._dayOverClass + ' a') + .trigger('mouseover') + .end(); + var numMonths = this._getNumberOfMonths(inst); + var cols = numMonths[1]; + var width = 17; + if (cols > 1) + inst.dpDiv.addClass('ui-datepicker-multi-' + cols).css('width', (width * cols) + 'em'); + else + inst.dpDiv.removeClass('ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4').width(''); + inst.dpDiv[(numMonths[0] != 1 || numMonths[1] != 1 ? 'add' : 'remove') + + 'Class']('ui-datepicker-multi'); + inst.dpDiv[(this._get(inst, 'isRTL') ? 'add' : 'remove') + + 'Class']('ui-datepicker-rtl'); + if (inst == $.datepicker._curInst && $.datepicker._datepickerShowing && inst.input && + // #6694 - don't focus the input if it's already focused + // this breaks the change event in IE + inst.input.is(':visible') && !inst.input.is(':disabled') && inst.input[0] != document.activeElement) + inst.input.focus(); + // deffered render of the years select (to avoid flashes on Firefox) + if( inst.yearshtml ){ + var origyearshtml = inst.yearshtml; + setTimeout(function(){ + //assure that inst.yearshtml didn't change. + if( origyearshtml === inst.yearshtml ){ + inst.dpDiv.find('select.ui-datepicker-year:first').replaceWith(inst.yearshtml); + } + origyearshtml = inst.yearshtml = null; + }, 0); + } + }, + + /* Retrieve the size of left and top borders for an element. + @param elem (jQuery object) the element of interest + @return (number[2]) the left and top borders */ + _getBorders: function(elem) { + var convert = function(value) { + return {thin: 1, medium: 2, thick: 3}[value] || value; + }; + return [parseFloat(convert(elem.css('border-left-width'))), + parseFloat(convert(elem.css('border-top-width')))]; + }, + + /* Check positioning to remain on screen. */ + _checkOffset: function(inst, offset, isFixed) { + var dpWidth = inst.dpDiv.outerWidth(); + var dpHeight = inst.dpDiv.outerHeight(); + var inputWidth = inst.input ? inst.input.outerWidth() : 0; + var inputHeight = inst.input ? inst.input.outerHeight() : 0; + var viewWidth = document.documentElement.clientWidth + $(document).scrollLeft(); + var viewHeight = document.documentElement.clientHeight + $(document).scrollTop(); + + offset.left -= (this._get(inst, 'isRTL') ? (dpWidth - inputWidth) : 0); + offset.left -= (isFixed && offset.left == inst.input.offset().left) ? $(document).scrollLeft() : 0; + offset.top -= (isFixed && offset.top == (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0; + + // now check if datepicker is showing outside window viewport - move to a better place if so. + offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? + Math.abs(offset.left + dpWidth - viewWidth) : 0); + offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? + Math.abs(dpHeight + inputHeight) : 0); + + return offset; + }, + + /* Find an object's position on the screen. */ + _findPos: function(obj) { + var inst = this._getInst(obj); + var isRTL = this._get(inst, 'isRTL'); + while (obj && (obj.type == 'hidden' || obj.nodeType != 1 || $.expr.filters.hidden(obj))) { + obj = obj[isRTL ? 'previousSibling' : 'nextSibling']; + } + var position = $(obj).offset(); + return [position.left, position.top]; + }, + + /* Hide the date picker from view. + @param input element - the input field attached to the date picker */ + _hideDatepicker: function(input) { + var inst = this._curInst; + if (!inst || (input && inst != $.data(input, PROP_NAME))) + return; + if (this._datepickerShowing) { + var showAnim = this._get(inst, 'showAnim'); + var duration = this._get(inst, 'duration'); + var postProcess = function() { + $.datepicker._tidyDialog(inst); + this._curInst = null; + }; + if ($.effects && $.effects[showAnim]) + inst.dpDiv.hide(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess); + else + inst.dpDiv[(showAnim == 'slideDown' ? 'slideUp' : + (showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))]((showAnim ? duration : null), postProcess); + if (!showAnim) + postProcess(); + var onClose = this._get(inst, 'onClose'); + if (onClose) + onClose.apply((inst.input ? inst.input[0] : null), + [(inst.input ? inst.input.val() : ''), inst]); // trigger custom callback + this._datepickerShowing = false; + this._lastInput = null; + if (this._inDialog) { + this._dialogInput.css({ position: 'absolute', left: '0', top: '-100px' }); + if ($.blockUI) { + $.unblockUI(); + $('body').append(this.dpDiv); + } + } + this._inDialog = false; + } + }, + + /* Tidy up after a dialog display. */ + _tidyDialog: function(inst) { + inst.dpDiv.removeClass(this._dialogClass).unbind('.ui-datepicker-calendar'); + }, + + /* Close date picker if clicked elsewhere. */ + _checkExternalClick: function(event) { + if (!$.datepicker._curInst) + return; + var $target = $(event.target); + if ($target[0].id != $.datepicker._mainDivId && + $target.parents('#' + $.datepicker._mainDivId).length == 0 && + !$target.hasClass($.datepicker.markerClassName) && + !$target.hasClass($.datepicker._triggerClass) && + $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI)) + $.datepicker._hideDatepicker(); + }, + + /* Adjust one of the date sub-fields. */ + _adjustDate: function(id, offset, period) { + var target = $(id); + var inst = this._getInst(target[0]); + if (this._isDisabledDatepicker(target[0])) { + return; + } + this._adjustInstDate(inst, offset + + (period == 'M' ? this._get(inst, 'showCurrentAtPos') : 0), // undo positioning + period); + this._updateDatepicker(inst); + }, + + /* Action for current link. */ + _gotoToday: function(id) { + var target = $(id); + var inst = this._getInst(target[0]); + if (this._get(inst, 'gotoCurrent') && inst.currentDay) { + inst.selectedDay = inst.currentDay; + inst.drawMonth = inst.selectedMonth = inst.currentMonth; + inst.drawYear = inst.selectedYear = inst.currentYear; + } + else { + var date = new Date(); + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + } + this._notifyChange(inst); + this._adjustDate(target); + }, + + /* Action for selecting a new month/year. */ + _selectMonthYear: function(id, select, period) { + var target = $(id); + var inst = this._getInst(target[0]); + inst._selectingMonthYear = false; + inst['selected' + (period == 'M' ? 'Month' : 'Year')] = + inst['draw' + (period == 'M' ? 'Month' : 'Year')] = + parseInt(select.options[select.selectedIndex].value,10); + this._notifyChange(inst); + this._adjustDate(target); + }, + + /* Restore input focus after not changing month/year. */ + _clickMonthYear: function(id) { + var target = $(id); + var inst = this._getInst(target[0]); + if (inst.input && inst._selectingMonthYear) { + setTimeout(function() { + inst.input.focus(); + }, 0); + } + inst._selectingMonthYear = !inst._selectingMonthYear; + }, + + /* Action for selecting a day. */ + _selectDay: function(id, month, year, td) { + var target = $(id); + if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) { + return; + } + var inst = this._getInst(target[0]); + inst.selectedDay = inst.currentDay = $('a', td).html(); + inst.selectedMonth = inst.currentMonth = month; + inst.selectedYear = inst.currentYear = year; + this._selectDate(id, this._formatDate(inst, + inst.currentDay, inst.currentMonth, inst.currentYear)); + }, + + /* Erase the input field and hide the date picker. */ + _clearDate: function(id) { + var target = $(id); + var inst = this._getInst(target[0]); + this._selectDate(target, ''); + }, + + /* Update the input field with the selected date. */ + _selectDate: function(id, dateStr) { + var target = $(id); + var inst = this._getInst(target[0]); + dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); + if (inst.input) + inst.input.val(dateStr); + this._updateAlternate(inst); + var onSelect = this._get(inst, 'onSelect'); + if (onSelect) + onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback + else if (inst.input) + inst.input.trigger('change'); // fire the change event + if (inst.inline) + this._updateDatepicker(inst); + else { + this._hideDatepicker(); + this._lastInput = inst.input[0]; + if (typeof(inst.input[0]) != 'object') + inst.input.focus(); // restore focus + this._lastInput = null; + } + }, + + /* Update any alternate field to synchronise with the main field. */ + _updateAlternate: function(inst) { + var altField = this._get(inst, 'altField'); + if (altField) { // update alternate field too + var altFormat = this._get(inst, 'altFormat') || this._get(inst, 'dateFormat'); + var date = this._getDate(inst); + var dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst)); + $(altField).each(function() { $(this).val(dateStr); }); + } + }, + + /* Set as beforeShowDay function to prevent selection of weekends. + @param date Date - the date to customise + @return [boolean, string] - is this date selectable?, what is its CSS class? */ + noWeekends: function(date) { + var day = date.getDay(); + return [(day > 0 && day < 6), '']; + }, + + /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. + @param date Date - the date to get the week for + @return number - the number of the week within the year that contains this date */ + iso8601Week: function(date) { + var checkDate = new Date(date.getTime()); + // Find Thursday of this week starting on Monday + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); + var time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + }, + + /* Parse a string value into a date object. + See formatDate below for the possible formats. + + @param format string - the expected format of the date + @param value string - the date in the above format + @param settings Object - attributes include: + shortYearCutoff number - the cutoff year for determining the century (optional) + dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + dayNames string[7] - names of the days from Sunday (optional) + monthNamesShort string[12] - abbreviated names of the months (optional) + monthNames string[12] - names of the months (optional) + @return Date - the extracted date value or null if value is blank */ + parseDate: function (format, value, settings) { + if (format == null || value == null) + throw 'Invalid arguments'; + value = (typeof value == 'object' ? value.toString() : value + ''); + if (value == '') + return null; + var shortYearCutoff = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff; + shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff : + new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); + var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort; + var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames; + var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort; + var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames; + var year = -1; + var month = -1; + var day = -1; + var doy = -1; + var literal = false; + // Check whether a format character is doubled + var lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match); + if (matches) + iFormat++; + return matches; + }; + // Extract a number from the string value + var getNumber = function(match) { + var isDoubled = lookAhead(match); + var size = (match == '@' ? 14 : (match == '!' ? 20 : + (match == 'y' && isDoubled ? 4 : (match == 'o' ? 3 : 2)))); + var digits = new RegExp('^\\d{1,' + size + '}'); + var num = value.substring(iValue).match(digits); + if (!num) + throw 'Missing number at position ' + iValue; + iValue += num[0].length; + return parseInt(num[0], 10); + }; + // Extract a name from the string value and convert to an index + var getName = function(match, shortNames, longNames) { + var names = (lookAhead(match) ? longNames : shortNames); + for (var i = 0; i < names.length; i++) { + if (value.substr(iValue, names[i].length).toLowerCase() == names[i].toLowerCase()) { + iValue += names[i].length; + return i + 1; + } + } + throw 'Unknown name at position ' + iValue; + }; + // Confirm that a literal character matches the string value + var checkLiteral = function() { + if (value.charAt(iValue) != format.charAt(iFormat)) + throw 'Unexpected literal at position ' + iValue; + iValue++; + }; + var iValue = 0; + for (var iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) + if (format.charAt(iFormat) == "'" && !lookAhead("'")) + literal = false; + else + checkLiteral(); + else + switch (format.charAt(iFormat)) { + case 'd': + day = getNumber('d'); + break; + case 'D': + getName('D', dayNamesShort, dayNames); + break; + case 'o': + doy = getNumber('o'); + break; + case 'm': + month = getNumber('m'); + break; + case 'M': + month = getName('M', monthNamesShort, monthNames); + break; + case 'y': + year = getNumber('y'); + break; + case '@': + var date = new Date(getNumber('@')); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case '!': + var date = new Date((getNumber('!') - this._ticksTo1970) / 10000); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "'": + if (lookAhead("'")) + checkLiteral(); + else + literal = true; + break; + default: + checkLiteral(); + } + } + if (year == -1) + year = new Date().getFullYear(); + else if (year < 100) + year += new Date().getFullYear() - new Date().getFullYear() % 100 + + (year <= shortYearCutoff ? 0 : -100); + if (doy > -1) { + month = 1; + day = doy; + do { + var dim = this._getDaysInMonth(year, month - 1); + if (day <= dim) + break; + month++; + day -= dim; + } while (true); + } + var date = this._daylightSavingAdjust(new Date(year, month - 1, day)); + if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day) + throw 'Invalid date'; // E.g. 31/02/* + return date; + }, + + /* Standard date formats. */ + ATOM: 'yy-mm-dd', // RFC 3339 (ISO 8601) + COOKIE: 'D, dd M yy', + ISO_8601: 'yy-mm-dd', + RFC_822: 'D, d M y', + RFC_850: 'DD, dd-M-y', + RFC_1036: 'D, d M y', + RFC_1123: 'D, d M yy', + RFC_2822: 'D, d M yy', + RSS: 'D, d M y', // RFC 822 + TICKS: '!', + TIMESTAMP: '@', + W3C: 'yy-mm-dd', // ISO 8601 + + _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + + Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000), + + /* Format a date object into a string value. + The format can be combinations of the following: + d - day of month (no leading zero) + dd - day of month (two digit) + o - day of year (no leading zeros) + oo - day of year (three digit) + D - day name short + DD - day name long + m - month of year (no leading zero) + mm - month of year (two digit) + M - month name short + MM - month name long + y - year (two digit) + yy - year (four digit) + @ - Unix timestamp (ms since 01/01/1970) + ! - Windows ticks (100ns since 01/01/0001) + '...' - literal text + '' - single quote + + @param format string - the desired format of the date + @param date Date - the date value to format + @param settings Object - attributes include: + dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + dayNames string[7] - names of the days from Sunday (optional) + monthNamesShort string[12] - abbreviated names of the months (optional) + monthNames string[12] - names of the months (optional) + @return string - the date in the above format */ + formatDate: function (format, date, settings) { + if (!date) + return ''; + var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort; + var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames; + var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort; + var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames; + // Check whether a format character is doubled + var lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match); + if (matches) + iFormat++; + return matches; + }; + // Format a number, with leading zero if necessary + var formatNumber = function(match, value, len) { + var num = '' + value; + if (lookAhead(match)) + while (num.length < len) + num = '0' + num; + return num; + }; + // Format a name, short or long as requested + var formatName = function(match, value, shortNames, longNames) { + return (lookAhead(match) ? longNames[value] : shortNames[value]); + }; + var output = ''; + var literal = false; + if (date) + for (var iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) + if (format.charAt(iFormat) == "'" && !lookAhead("'")) + literal = false; + else + output += format.charAt(iFormat); + else + switch (format.charAt(iFormat)) { + case 'd': + output += formatNumber('d', date.getDate(), 2); + break; + case 'D': + output += formatName('D', date.getDay(), dayNamesShort, dayNames); + break; + case 'o': + output += formatNumber('o', + (date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000, 3); + break; + case 'm': + output += formatNumber('m', date.getMonth() + 1, 2); + break; + case 'M': + output += formatName('M', date.getMonth(), monthNamesShort, monthNames); + break; + case 'y': + output += (lookAhead('y') ? date.getFullYear() : + (date.getYear() % 100 < 10 ? '0' : '') + date.getYear() % 100); + break; + case '@': + output += date.getTime(); + break; + case '!': + output += date.getTime() * 10000 + this._ticksTo1970; + break; + case "'": + if (lookAhead("'")) + output += "'"; + else + literal = true; + break; + default: + output += format.charAt(iFormat); + } + } + return output; + }, + + /* Extract all possible characters from the date format. */ + _possibleChars: function (format) { + var chars = ''; + var literal = false; + // Check whether a format character is doubled + var lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match); + if (matches) + iFormat++; + return matches; + }; + for (var iFormat = 0; iFormat < format.length; iFormat++) + if (literal) + if (format.charAt(iFormat) == "'" && !lookAhead("'")) + literal = false; + else + chars += format.charAt(iFormat); + else + switch (format.charAt(iFormat)) { + case 'd': case 'm': case 'y': case '@': + chars += '0123456789'; + break; + case 'D': case 'M': + return null; // Accept anything + case "'": + if (lookAhead("'")) + chars += "'"; + else + literal = true; + break; + default: + chars += format.charAt(iFormat); + } + return chars; + }, + + /* Get a setting value, defaulting if necessary. */ + _get: function(inst, name) { + return inst.settings[name] !== undefined ? + inst.settings[name] : this._defaults[name]; + }, + + /* Parse existing date and initialise date picker. */ + _setDateFromField: function(inst, noDefault) { + if (inst.input.val() == inst.lastVal) { + return; + } + var dateFormat = this._get(inst, 'dateFormat'); + var dates = inst.lastVal = inst.input ? inst.input.val() : null; + var date, defaultDate; + date = defaultDate = this._getDefaultDate(inst); + var settings = this._getFormatConfig(inst); + try { + date = this.parseDate(dateFormat, dates, settings) || defaultDate; + } catch (event) { + this.log(event); + dates = (noDefault ? '' : dates); + } + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + inst.currentDay = (dates ? date.getDate() : 0); + inst.currentMonth = (dates ? date.getMonth() : 0); + inst.currentYear = (dates ? date.getFullYear() : 0); + this._adjustInstDate(inst); + }, + + /* Retrieve the default date shown on opening. */ + _getDefaultDate: function(inst) { + return this._restrictMinMax(inst, + this._determineDate(inst, this._get(inst, 'defaultDate'), new Date())); + }, + + /* A date may be specified as an exact value or a relative one. */ + _determineDate: function(inst, date, defaultDate) { + var offsetNumeric = function(offset) { + var date = new Date(); + date.setDate(date.getDate() + offset); + return date; + }; + var offsetString = function(offset) { + try { + return $.datepicker.parseDate($.datepicker._get(inst, 'dateFormat'), + offset, $.datepicker._getFormatConfig(inst)); + } + catch (e) { + // Ignore + } + var date = (offset.toLowerCase().match(/^c/) ? + $.datepicker._getDate(inst) : null) || new Date(); + var year = date.getFullYear(); + var month = date.getMonth(); + var day = date.getDate(); + var pattern = /([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g; + var matches = pattern.exec(offset); + while (matches) { + switch (matches[2] || 'd') { + case 'd' : case 'D' : + day += parseInt(matches[1],10); break; + case 'w' : case 'W' : + day += parseInt(matches[1],10) * 7; break; + case 'm' : case 'M' : + month += parseInt(matches[1],10); + day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); + break; + case 'y': case 'Y' : + year += parseInt(matches[1],10); + day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); + break; + } + matches = pattern.exec(offset); + } + return new Date(year, month, day); + }; + var newDate = (date == null || date === '' ? defaultDate : (typeof date == 'string' ? offsetString(date) : + (typeof date == 'number' ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime())))); + newDate = (newDate && newDate.toString() == 'Invalid Date' ? defaultDate : newDate); + if (newDate) { + newDate.setHours(0); + newDate.setMinutes(0); + newDate.setSeconds(0); + newDate.setMilliseconds(0); + } + return this._daylightSavingAdjust(newDate); + }, + + /* Handle switch to/from daylight saving. + Hours may be non-zero on daylight saving cut-over: + > 12 when midnight changeover, but then cannot generate + midnight datetime, so jump to 1AM, otherwise reset. + @param date (Date) the date to check + @return (Date) the corrected date */ + _daylightSavingAdjust: function(date) { + if (!date) return null; + date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0); + return date; + }, + + /* Set the date(s) directly. */ + _setDate: function(inst, date, noChange) { + var clear = !date; + var origMonth = inst.selectedMonth; + var origYear = inst.selectedYear; + var newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date())); + inst.selectedDay = inst.currentDay = newDate.getDate(); + inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); + inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); + if ((origMonth != inst.selectedMonth || origYear != inst.selectedYear) && !noChange) + this._notifyChange(inst); + this._adjustInstDate(inst); + if (inst.input) { + inst.input.val(clear ? '' : this._formatDate(inst)); + } + }, + + /* Retrieve the date(s) directly. */ + _getDate: function(inst) { + var startDate = (!inst.currentYear || (inst.input && inst.input.val() == '') ? null : + this._daylightSavingAdjust(new Date( + inst.currentYear, inst.currentMonth, inst.currentDay))); + return startDate; + }, + + /* Generate the HTML for the current state of the date picker. */ + _generateHTML: function(inst) { + var today = new Date(); + today = this._daylightSavingAdjust( + new Date(today.getFullYear(), today.getMonth(), today.getDate())); // clear time + var isRTL = this._get(inst, 'isRTL'); + var showButtonPanel = this._get(inst, 'showButtonPanel'); + var hideIfNoPrevNext = this._get(inst, 'hideIfNoPrevNext'); + var navigationAsDateFormat = this._get(inst, 'navigationAsDateFormat'); + var numMonths = this._getNumberOfMonths(inst); + var showCurrentAtPos = this._get(inst, 'showCurrentAtPos'); + var stepMonths = this._get(inst, 'stepMonths'); + var isMultiMonth = (numMonths[0] != 1 || numMonths[1] != 1); + var currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) : + new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); + var minDate = this._getMinMaxDate(inst, 'min'); + var maxDate = this._getMinMaxDate(inst, 'max'); + var drawMonth = inst.drawMonth - showCurrentAtPos; + var drawYear = inst.drawYear; + if (drawMonth < 0) { + drawMonth += 12; + drawYear--; + } + if (maxDate) { + var maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(), + maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate())); + maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw); + while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) { + drawMonth--; + if (drawMonth < 0) { + drawMonth = 11; + drawYear--; + } + } + } + inst.drawMonth = drawMonth; + inst.drawYear = drawYear; + var prevText = this._get(inst, 'prevText'); + prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText, + this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)), + this._getFormatConfig(inst))); + var prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ? + '' + prevText + '' : + (hideIfNoPrevNext ? '' : '' + prevText + '')); + var nextText = this._get(inst, 'nextText'); + nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText, + this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)), + this._getFormatConfig(inst))); + var next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ? + '' + nextText + '' : + (hideIfNoPrevNext ? '' : '' + nextText + '')); + var currentText = this._get(inst, 'currentText'); + var gotoDate = (this._get(inst, 'gotoCurrent') && inst.currentDay ? currentDate : today); + currentText = (!navigationAsDateFormat ? currentText : + this.formatDate(currentText, gotoDate, this._getFormatConfig(inst))); + var controls = (!inst.inline ? '' : ''); + var buttonPanel = (showButtonPanel) ? '
      ' + (isRTL ? controls : '') + + (this._isInRange(inst, gotoDate) ? '' : '') + (isRTL ? '' : controls) + '
      ' : ''; + var firstDay = parseInt(this._get(inst, 'firstDay'),10); + firstDay = (isNaN(firstDay) ? 0 : firstDay); + var showWeek = this._get(inst, 'showWeek'); + var dayNames = this._get(inst, 'dayNames'); + var dayNamesShort = this._get(inst, 'dayNamesShort'); + var dayNamesMin = this._get(inst, 'dayNamesMin'); + var monthNames = this._get(inst, 'monthNames'); + var monthNamesShort = this._get(inst, 'monthNamesShort'); + var beforeShowDay = this._get(inst, 'beforeShowDay'); + var showOtherMonths = this._get(inst, 'showOtherMonths'); + var selectOtherMonths = this._get(inst, 'selectOtherMonths'); + var calculateWeek = this._get(inst, 'calculateWeek') || this.iso8601Week; + var defaultDate = this._getDefaultDate(inst); + var html = ''; + for (var row = 0; row < numMonths[0]; row++) { + var group = ''; + for (var col = 0; col < numMonths[1]; col++) { + var selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); + var cornerClass = ' ui-corner-all'; + var calender = ''; + if (isMultiMonth) { + calender += '
      '; + } + calender += '
      ' + + (/all|left/.test(cornerClass) && row == 0 ? (isRTL ? next : prev) : '') + + (/all|right/.test(cornerClass) && row == 0 ? (isRTL ? prev : next) : '') + + this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, + row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers + '
      ' + + ''; + var thead = (showWeek ? '' : ''); + for (var dow = 0; dow < 7; dow++) { // days of the week + var day = (dow + firstDay) % 7; + thead += '= 5 ? ' class="ui-datepicker-week-end"' : '') + '>' + + '' + dayNamesMin[day] + ''; + } + calender += thead + ''; + var daysInMonth = this._getDaysInMonth(drawYear, drawMonth); + if (drawYear == inst.selectedYear && drawMonth == inst.selectedMonth) + inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); + var leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7; + var numRows = (isMultiMonth ? 6 : Math.ceil((leadDays + daysInMonth) / 7)); // calculate the number of rows to generate + var printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); + for (var dRow = 0; dRow < numRows; dRow++) { // create date picker rows + calender += ''; + var tbody = (!showWeek ? '' : ''); + for (var dow = 0; dow < 7; dow++) { // create date picker days + var daySettings = (beforeShowDay ? + beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, '']); + var otherMonth = (printDate.getMonth() != drawMonth); + var unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] || + (minDate && printDate < minDate) || (maxDate && printDate > maxDate); + tbody += ''; // display selectable date + printDate.setDate(printDate.getDate() + 1); + printDate = this._daylightSavingAdjust(printDate); + } + calender += tbody + ''; + } + drawMonth++; + if (drawMonth > 11) { + drawMonth = 0; + drawYear++; + } + calender += '
      ' + this._get(inst, 'weekHeader') + '
      ' + + this._get(inst, 'calculateWeek')(printDate) + '' + // actions + (otherMonth && !showOtherMonths ? ' ' : // display for other months + (unselectable ? '' + printDate.getDate() + '' : '' + printDate.getDate() + '')) + '
      ' + (isMultiMonth ? '
      ' + + ((numMonths[0] > 0 && col == numMonths[1]-1) ? '
      ' : '') : ''); + group += calender; + } + html += group; + } + html += buttonPanel + ($.browser.msie && parseInt($.browser.version,10) < 7 && !inst.inline ? + '' : ''); + inst._keyEvent = false; + return html; + }, + + /* Generate the month and year header. */ + _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate, + secondary, monthNames, monthNamesShort) { + var changeMonth = this._get(inst, 'changeMonth'); + var changeYear = this._get(inst, 'changeYear'); + var showMonthAfterYear = this._get(inst, 'showMonthAfterYear'); + var html = '
      '; + var monthHtml = ''; + // month selection + if (secondary || !changeMonth) + monthHtml += '' + monthNames[drawMonth] + ''; + else { + var inMinYear = (minDate && minDate.getFullYear() == drawYear); + var inMaxYear = (maxDate && maxDate.getFullYear() == drawYear); + monthHtml += ''; + } + if (!showMonthAfterYear) + html += monthHtml + (secondary || !(changeMonth && changeYear) ? ' ' : ''); + // year selection + inst.yearshtml = ''; + if (secondary || !changeYear) + html += '' + drawYear + ''; + else { + // determine range of years to display + var years = this._get(inst, 'yearRange').split(':'); + var thisYear = new Date().getFullYear(); + var determineYear = function(value) { + var year = (value.match(/c[+-].*/) ? drawYear + parseInt(value.substring(1), 10) : + (value.match(/[+-].*/) ? thisYear + parseInt(value, 10) : + parseInt(value, 10))); + return (isNaN(year) ? thisYear : year); + }; + var year = determineYear(years[0]); + var endYear = Math.max(year, determineYear(years[1] || '')); + year = (minDate ? Math.max(year, minDate.getFullYear()) : year); + endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear); + inst.yearshtml += ''; + //when showing there is no need for later update + if( ! $.browser.mozilla ){ + html += inst.yearshtml; + inst.yearshtml = null; + } else { + // will be replaced later with inst.yearshtml + html += ''; + } + } + html += this._get(inst, 'yearSuffix'); + if (showMonthAfterYear) + html += (secondary || !(changeMonth && changeYear) ? ' ' : '') + monthHtml; + html += '
      '; // Close datepicker_header + return html; + }, + + /* Adjust one of the date sub-fields. */ + _adjustInstDate: function(inst, offset, period) { + var year = inst.drawYear + (period == 'Y' ? offset : 0); + var month = inst.drawMonth + (period == 'M' ? offset : 0); + var day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + + (period == 'D' ? offset : 0); + var date = this._restrictMinMax(inst, + this._daylightSavingAdjust(new Date(year, month, day))); + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + if (period == 'M' || period == 'Y') + this._notifyChange(inst); + }, + + /* Ensure a date is within any min/max bounds. */ + _restrictMinMax: function(inst, date) { + var minDate = this._getMinMaxDate(inst, 'min'); + var maxDate = this._getMinMaxDate(inst, 'max'); + var newDate = (minDate && date < minDate ? minDate : date); + newDate = (maxDate && newDate > maxDate ? maxDate : newDate); + return newDate; + }, + + /* Notify change of month/year. */ + _notifyChange: function(inst) { + var onChange = this._get(inst, 'onChangeMonthYear'); + if (onChange) + onChange.apply((inst.input ? inst.input[0] : null), + [inst.selectedYear, inst.selectedMonth + 1, inst]); + }, + + /* Determine the number of months to show. */ + _getNumberOfMonths: function(inst) { + var numMonths = this._get(inst, 'numberOfMonths'); + return (numMonths == null ? [1, 1] : (typeof numMonths == 'number' ? [1, numMonths] : numMonths)); + }, + + /* Determine the current maximum date - ensure no time components are set. */ + _getMinMaxDate: function(inst, minMax) { + return this._determineDate(inst, this._get(inst, minMax + 'Date'), null); + }, + + /* Find the number of days in a given month. */ + _getDaysInMonth: function(year, month) { + return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate(); + }, + + /* Find the day of the week of the first of a month. */ + _getFirstDayOfMonth: function(year, month) { + return new Date(year, month, 1).getDay(); + }, + + /* Determines if we should allow a "next/prev" month display change. */ + _canAdjustMonth: function(inst, offset, curYear, curMonth) { + var numMonths = this._getNumberOfMonths(inst); + var date = this._daylightSavingAdjust(new Date(curYear, + curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1)); + if (offset < 0) + date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth())); + return this._isInRange(inst, date); + }, + + /* Is the given date in the accepted range? */ + _isInRange: function(inst, date) { + var minDate = this._getMinMaxDate(inst, 'min'); + var maxDate = this._getMinMaxDate(inst, 'max'); + return ((!minDate || date.getTime() >= minDate.getTime()) && + (!maxDate || date.getTime() <= maxDate.getTime())); + }, + + /* Provide the configuration settings for formatting/parsing. */ + _getFormatConfig: function(inst) { + var shortYearCutoff = this._get(inst, 'shortYearCutoff'); + shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff : + new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); + return {shortYearCutoff: shortYearCutoff, + dayNamesShort: this._get(inst, 'dayNamesShort'), dayNames: this._get(inst, 'dayNames'), + monthNamesShort: this._get(inst, 'monthNamesShort'), monthNames: this._get(inst, 'monthNames')}; + }, + + /* Format the given date for display. */ + _formatDate: function(inst, day, month, year) { + if (!day) { + inst.currentDay = inst.selectedDay; + inst.currentMonth = inst.selectedMonth; + inst.currentYear = inst.selectedYear; + } + var date = (day ? (typeof day == 'object' ? day : + this._daylightSavingAdjust(new Date(year, month, day))) : + this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); + return this.formatDate(this._get(inst, 'dateFormat'), date, this._getFormatConfig(inst)); + } +}); + +/* jQuery extend now ignores nulls! */ +function extendRemove(target, props) { + $.extend(target, props); + for (var name in props) + if (props[name] == null || props[name] == undefined) + target[name] = props[name]; + return target; +}; + +/* Determine whether an object is an array. */ +function isArray(a) { + return (a && (($.browser.safari && typeof a == 'object' && a.length) || + (a.constructor && a.constructor.toString().match(/\Array\(\)/)))); +}; + +/* Invoke the datepicker functionality. + @param options string - a command, optionally followed by additional parameters or + Object - settings for attaching new datepicker functionality + @return jQuery object */ +$.fn.datepicker = function(options){ + + /* Verify an empty collection wasn't passed - Fixes #6976 */ + if ( !this.length ) { + return this; + } + + /* Initialise the date picker. */ + if (!$.datepicker.initialized) { + $(document).mousedown($.datepicker._checkExternalClick). + find('body').append($.datepicker.dpDiv); + $.datepicker.initialized = true; + } + + var otherArgs = Array.prototype.slice.call(arguments, 1); + if (typeof options == 'string' && (options == 'isDisabled' || options == 'getDate' || options == 'widget')) + return $.datepicker['_' + options + 'Datepicker']. + apply($.datepicker, [this[0]].concat(otherArgs)); + if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string') + return $.datepicker['_' + options + 'Datepicker']. + apply($.datepicker, [this[0]].concat(otherArgs)); + return this.each(function() { + typeof options == 'string' ? + $.datepicker['_' + options + 'Datepicker']. + apply($.datepicker, [this].concat(otherArgs)) : + $.datepicker._attachDatepicker(this, options); + }); +}; + +$.datepicker = new Datepicker(); // singleton instance +$.datepicker.initialized = false; +$.datepicker.uuid = new Date().getTime(); +$.datepicker.version = "1.8.11"; + +// Workaround for #4055 +// Add another global to avoid noConflict issues with inline event handlers +window['DP_jQuery_' + dpuuid] = $; + +})(jQuery); +/* + * jQuery UI Dialog 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.button.js + * jquery.ui.draggable.js + * jquery.ui.mouse.js + * jquery.ui.position.js + * jquery.ui.resizable.js + */ +(function( $, undefined ) { + +var uiDialogClasses = + 'ui-dialog ' + + 'ui-widget ' + + 'ui-widget-content ' + + 'ui-corner-all ', + sizeRelatedOptions = { + buttons: true, + height: true, + maxHeight: true, + maxWidth: true, + minHeight: true, + minWidth: true, + width: true + }, + resizableRelatedOptions = { + maxHeight: true, + maxWidth: true, + minHeight: true, + minWidth: true + }; + +$.widget("ui.dialog", { + options: { + autoOpen: true, + buttons: {}, + closeOnEscape: true, + closeText: 'close', + dialogClass: '', + draggable: true, + hide: null, + height: 'auto', + maxHeight: false, + maxWidth: false, + minHeight: 150, + minWidth: 150, + modal: false, + position: { + my: 'center', + at: 'center', + collision: 'fit', + // ensure that the titlebar is never outside the document + using: function(pos) { + var topOffset = $(this).css(pos).offset().top; + if (topOffset < 0) { + $(this).css('top', pos.top - topOffset); + } + } + }, + resizable: true, + show: null, + stack: true, + title: '', + width: 300, + zIndex: 1000 + }, + + _create: function() { + this.originalTitle = this.element.attr('title'); + // #5742 - .attr() might return a DOMElement + if ( typeof this.originalTitle !== "string" ) { + this.originalTitle = ""; + } + + this.options.title = this.options.title || this.originalTitle; + var self = this, + options = self.options, + + title = options.title || ' ', + titleId = $.ui.dialog.getTitleId(self.element), + + uiDialog = (self.uiDialog = $('
      ')) + .appendTo(document.body) + .hide() + .addClass(uiDialogClasses + options.dialogClass) + .css({ + zIndex: options.zIndex + }) + // setting tabIndex makes the div focusable + // setting outline to 0 prevents a border on focus in Mozilla + .attr('tabIndex', -1).css('outline', 0).keydown(function(event) { + if (options.closeOnEscape && event.keyCode && + event.keyCode === $.ui.keyCode.ESCAPE) { + + self.close(event); + event.preventDefault(); + } + }) + .attr({ + role: 'dialog', + 'aria-labelledby': titleId + }) + .mousedown(function(event) { + self.moveToTop(false, event); + }), + + uiDialogContent = self.element + .show() + .removeAttr('title') + .addClass( + 'ui-dialog-content ' + + 'ui-widget-content') + .appendTo(uiDialog), + + uiDialogTitlebar = (self.uiDialogTitlebar = $('
      ')) + .addClass( + 'ui-dialog-titlebar ' + + 'ui-widget-header ' + + 'ui-corner-all ' + + 'ui-helper-clearfix' + ) + .prependTo(uiDialog), + + uiDialogTitlebarClose = $('') + .addClass( + 'ui-dialog-titlebar-close ' + + 'ui-corner-all' + ) + .attr('role', 'button') + .hover( + function() { + uiDialogTitlebarClose.addClass('ui-state-hover'); + }, + function() { + uiDialogTitlebarClose.removeClass('ui-state-hover'); + } + ) + .focus(function() { + uiDialogTitlebarClose.addClass('ui-state-focus'); + }) + .blur(function() { + uiDialogTitlebarClose.removeClass('ui-state-focus'); + }) + .click(function(event) { + self.close(event); + return false; + }) + .appendTo(uiDialogTitlebar), + + uiDialogTitlebarCloseText = (self.uiDialogTitlebarCloseText = $('')) + .addClass( + 'ui-icon ' + + 'ui-icon-closethick' + ) + .text(options.closeText) + .appendTo(uiDialogTitlebarClose), + + uiDialogTitle = $('') + .addClass('ui-dialog-title') + .attr('id', titleId) + .html(title) + .prependTo(uiDialogTitlebar); + + //handling of deprecated beforeclose (vs beforeClose) option + //Ticket #4669 http://dev.jqueryui.com/ticket/4669 + //TODO: remove in 1.9pre + if ($.isFunction(options.beforeclose) && !$.isFunction(options.beforeClose)) { + options.beforeClose = options.beforeclose; + } + + uiDialogTitlebar.find("*").add(uiDialogTitlebar).disableSelection(); + + if (options.draggable && $.fn.draggable) { + self._makeDraggable(); + } + if (options.resizable && $.fn.resizable) { + self._makeResizable(); + } + + self._createButtons(options.buttons); + self._isOpen = false; + + if ($.fn.bgiframe) { + uiDialog.bgiframe(); + } + }, + + _init: function() { + if ( this.options.autoOpen ) { + this.open(); + } + }, + + destroy: function() { + var self = this; + + if (self.overlay) { + self.overlay.destroy(); + } + self.uiDialog.hide(); + self.element + .unbind('.dialog') + .removeData('dialog') + .removeClass('ui-dialog-content ui-widget-content') + .hide().appendTo('body'); + self.uiDialog.remove(); + + if (self.originalTitle) { + self.element.attr('title', self.originalTitle); + } + + return self; + }, + + widget: function() { + return this.uiDialog; + }, + + close: function(event) { + var self = this, + maxZ, thisZ; + + if (false === self._trigger('beforeClose', event)) { + return; + } + + if (self.overlay) { + self.overlay.destroy(); + } + self.uiDialog.unbind('keypress.ui-dialog'); + + self._isOpen = false; + + if (self.options.hide) { + self.uiDialog.hide(self.options.hide, function() { + self._trigger('close', event); + }); + } else { + self.uiDialog.hide(); + self._trigger('close', event); + } + + $.ui.dialog.overlay.resize(); + + // adjust the maxZ to allow other modal dialogs to continue to work (see #4309) + if (self.options.modal) { + maxZ = 0; + $('.ui-dialog').each(function() { + if (this !== self.uiDialog[0]) { + thisZ = $(this).css('z-index'); + if(!isNaN(thisZ)) { + maxZ = Math.max(maxZ, thisZ); + } + } + }); + $.ui.dialog.maxZ = maxZ; + } + + return self; + }, + + isOpen: function() { + return this._isOpen; + }, + + // the force parameter allows us to move modal dialogs to their correct + // position on open + moveToTop: function(force, event) { + var self = this, + options = self.options, + saveScroll; + + if ((options.modal && !force) || + (!options.stack && !options.modal)) { + return self._trigger('focus', event); + } + + if (options.zIndex > $.ui.dialog.maxZ) { + $.ui.dialog.maxZ = options.zIndex; + } + if (self.overlay) { + $.ui.dialog.maxZ += 1; + self.overlay.$el.css('z-index', $.ui.dialog.overlay.maxZ = $.ui.dialog.maxZ); + } + + //Save and then restore scroll since Opera 9.5+ resets when parent z-Index is changed. + // http://ui.jquery.com/bugs/ticket/3193 + saveScroll = { scrollTop: self.element.attr('scrollTop'), scrollLeft: self.element.attr('scrollLeft') }; + $.ui.dialog.maxZ += 1; + self.uiDialog.css('z-index', $.ui.dialog.maxZ); + self.element.attr(saveScroll); + self._trigger('focus', event); + + return self; + }, + + open: function() { + if (this._isOpen) { return; } + + var self = this, + options = self.options, + uiDialog = self.uiDialog; + + self.overlay = options.modal ? new $.ui.dialog.overlay(self) : null; + self._size(); + self._position(options.position); + uiDialog.show(options.show); + self.moveToTop(true); + + // prevent tabbing out of modal dialogs + if (options.modal) { + uiDialog.bind('keypress.ui-dialog', function(event) { + if (event.keyCode !== $.ui.keyCode.TAB) { + return; + } + + var tabbables = $(':tabbable', this), + first = tabbables.filter(':first'), + last = tabbables.filter(':last'); + + if (event.target === last[0] && !event.shiftKey) { + first.focus(1); + return false; + } else if (event.target === first[0] && event.shiftKey) { + last.focus(1); + return false; + } + }); + } + + // set focus to the first tabbable element in the content area or the first button + // if there are no tabbable elements, set focus on the dialog itself + $(self.element.find(':tabbable').get().concat( + uiDialog.find('.ui-dialog-buttonpane :tabbable').get().concat( + uiDialog.get()))).eq(0).focus(); + + self._isOpen = true; + self._trigger('open'); + + return self; + }, + + _createButtons: function(buttons) { + var self = this, + hasButtons = false, + uiDialogButtonPane = $('
      ') + .addClass( + 'ui-dialog-buttonpane ' + + 'ui-widget-content ' + + 'ui-helper-clearfix' + ), + uiButtonSet = $( "
      " ) + .addClass( "ui-dialog-buttonset" ) + .appendTo( uiDialogButtonPane ); + + // if we already have a button pane, remove it + self.uiDialog.find('.ui-dialog-buttonpane').remove(); + + if (typeof buttons === 'object' && buttons !== null) { + $.each(buttons, function() { + return !(hasButtons = true); + }); + } + if (hasButtons) { + $.each(buttons, function(name, props) { + props = $.isFunction( props ) ? + { click: props, text: name } : + props; + var button = $('') + .attr( props, true ) + .unbind('click') + .click(function() { + props.click.apply(self.element[0], arguments); + }) + .appendTo(uiButtonSet); + if ($.fn.button) { + button.button(); + } + }); + uiDialogButtonPane.appendTo(self.uiDialog); + } + }, + + _makeDraggable: function() { + var self = this, + options = self.options, + doc = $(document), + heightBeforeDrag; + + function filteredUi(ui) { + return { + position: ui.position, + offset: ui.offset + }; + } + + self.uiDialog.draggable({ + cancel: '.ui-dialog-content, .ui-dialog-titlebar-close', + handle: '.ui-dialog-titlebar', + containment: 'document', + start: function(event, ui) { + heightBeforeDrag = options.height === "auto" ? "auto" : $(this).height(); + $(this).height($(this).height()).addClass("ui-dialog-dragging"); + self._trigger('dragStart', event, filteredUi(ui)); + }, + drag: function(event, ui) { + self._trigger('drag', event, filteredUi(ui)); + }, + stop: function(event, ui) { + options.position = [ui.position.left - doc.scrollLeft(), + ui.position.top - doc.scrollTop()]; + $(this).removeClass("ui-dialog-dragging").height(heightBeforeDrag); + self._trigger('dragStop', event, filteredUi(ui)); + $.ui.dialog.overlay.resize(); + } + }); + }, + + _makeResizable: function(handles) { + handles = (handles === undefined ? this.options.resizable : handles); + var self = this, + options = self.options, + // .ui-resizable has position: relative defined in the stylesheet + // but dialogs have to use absolute or fixed positioning + position = self.uiDialog.css('position'), + resizeHandles = (typeof handles === 'string' ? + handles : + 'n,e,s,w,se,sw,ne,nw' + ); + + function filteredUi(ui) { + return { + originalPosition: ui.originalPosition, + originalSize: ui.originalSize, + position: ui.position, + size: ui.size + }; + } + + self.uiDialog.resizable({ + cancel: '.ui-dialog-content', + containment: 'document', + alsoResize: self.element, + maxWidth: options.maxWidth, + maxHeight: options.maxHeight, + minWidth: options.minWidth, + minHeight: self._minHeight(), + handles: resizeHandles, + start: function(event, ui) { + $(this).addClass("ui-dialog-resizing"); + self._trigger('resizeStart', event, filteredUi(ui)); + }, + resize: function(event, ui) { + self._trigger('resize', event, filteredUi(ui)); + }, + stop: function(event, ui) { + $(this).removeClass("ui-dialog-resizing"); + options.height = $(this).height(); + options.width = $(this).width(); + self._trigger('resizeStop', event, filteredUi(ui)); + $.ui.dialog.overlay.resize(); + } + }) + .css('position', position) + .find('.ui-resizable-se').addClass('ui-icon ui-icon-grip-diagonal-se'); + }, + + _minHeight: function() { + var options = this.options; + + if (options.height === 'auto') { + return options.minHeight; + } else { + return Math.min(options.minHeight, options.height); + } + }, + + _position: function(position) { + var myAt = [], + offset = [0, 0], + isVisible; + + if (position) { + // deep extending converts arrays to objects in jQuery <= 1.3.2 :-( + // if (typeof position == 'string' || $.isArray(position)) { + // myAt = $.isArray(position) ? position : position.split(' '); + + if (typeof position === 'string' || (typeof position === 'object' && '0' in position)) { + myAt = position.split ? position.split(' ') : [position[0], position[1]]; + if (myAt.length === 1) { + myAt[1] = myAt[0]; + } + + $.each(['left', 'top'], function(i, offsetPosition) { + if (+myAt[i] === myAt[i]) { + offset[i] = myAt[i]; + myAt[i] = offsetPosition; + } + }); + + position = { + my: myAt.join(" "), + at: myAt.join(" "), + offset: offset.join(" ") + }; + } + + position = $.extend({}, $.ui.dialog.prototype.options.position, position); + } else { + position = $.ui.dialog.prototype.options.position; + } + + // need to show the dialog to get the actual offset in the position plugin + isVisible = this.uiDialog.is(':visible'); + if (!isVisible) { + this.uiDialog.show(); + } + this.uiDialog + // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781 + .css({ top: 0, left: 0 }) + .position($.extend({ of: window }, position)); + if (!isVisible) { + this.uiDialog.hide(); + } + }, + + _setOptions: function( options ) { + var self = this, + resizableOptions = {}, + resize = false; + + $.each( options, function( key, value ) { + self._setOption( key, value ); + + if ( key in sizeRelatedOptions ) { + resize = true; + } + if ( key in resizableRelatedOptions ) { + resizableOptions[ key ] = value; + } + }); + + if ( resize ) { + this._size(); + } + if ( this.uiDialog.is( ":data(resizable)" ) ) { + this.uiDialog.resizable( "option", resizableOptions ); + } + }, + + _setOption: function(key, value){ + var self = this, + uiDialog = self.uiDialog; + + switch (key) { + //handling of deprecated beforeclose (vs beforeClose) option + //Ticket #4669 http://dev.jqueryui.com/ticket/4669 + //TODO: remove in 1.9pre + case "beforeclose": + key = "beforeClose"; + break; + case "buttons": + self._createButtons(value); + break; + case "closeText": + // ensure that we always pass a string + self.uiDialogTitlebarCloseText.text("" + value); + break; + case "dialogClass": + uiDialog + .removeClass(self.options.dialogClass) + .addClass(uiDialogClasses + value); + break; + case "disabled": + if (value) { + uiDialog.addClass('ui-dialog-disabled'); + } else { + uiDialog.removeClass('ui-dialog-disabled'); + } + break; + case "draggable": + var isDraggable = uiDialog.is( ":data(draggable)" ); + if ( isDraggable && !value ) { + uiDialog.draggable( "destroy" ); + } + + if ( !isDraggable && value ) { + self._makeDraggable(); + } + break; + case "position": + self._position(value); + break; + case "resizable": + // currently resizable, becoming non-resizable + var isResizable = uiDialog.is( ":data(resizable)" ); + if (isResizable && !value) { + uiDialog.resizable('destroy'); + } + + // currently resizable, changing handles + if (isResizable && typeof value === 'string') { + uiDialog.resizable('option', 'handles', value); + } + + // currently non-resizable, becoming resizable + if (!isResizable && value !== false) { + self._makeResizable(value); + } + break; + case "title": + // convert whatever was passed in o a string, for html() to not throw up + $(".ui-dialog-title", self.uiDialogTitlebar).html("" + (value || ' ')); + break; + } + + $.Widget.prototype._setOption.apply(self, arguments); + }, + + _size: function() { + /* If the user has resized the dialog, the .ui-dialog and .ui-dialog-content + * divs will both have width and height set, so we need to reset them + */ + var options = this.options, + nonContentHeight, + minContentHeight, + isVisible = this.uiDialog.is( ":visible" ); + + // reset content sizing + this.element.show().css({ + width: 'auto', + minHeight: 0, + height: 0 + }); + + if (options.minWidth > options.width) { + options.width = options.minWidth; + } + + // reset wrapper sizing + // determine the height of all the non-content elements + nonContentHeight = this.uiDialog.css({ + height: 'auto', + width: options.width + }) + .height(); + minContentHeight = Math.max( 0, options.minHeight - nonContentHeight ); + + if ( options.height === "auto" ) { + // only needed for IE6 support + if ( $.support.minHeight ) { + this.element.css({ + minHeight: minContentHeight, + height: "auto" + }); + } else { + this.uiDialog.show(); + var autoHeight = this.element.css( "height", "auto" ).height(); + if ( !isVisible ) { + this.uiDialog.hide(); + } + this.element.height( Math.max( autoHeight, minContentHeight ) ); + } + } else { + this.element.height( Math.max( options.height - nonContentHeight, 0 ) ); + } + + if (this.uiDialog.is(':data(resizable)')) { + this.uiDialog.resizable('option', 'minHeight', this._minHeight()); + } + } +}); + +$.extend($.ui.dialog, { + version: "1.8.11", + + uuid: 0, + maxZ: 0, + + getTitleId: function($el) { + var id = $el.attr('id'); + if (!id) { + this.uuid += 1; + id = this.uuid; + } + return 'ui-dialog-title-' + id; + }, + + overlay: function(dialog) { + this.$el = $.ui.dialog.overlay.create(dialog); + } +}); + +$.extend($.ui.dialog.overlay, { + instances: [], + // reuse old instances due to IE memory leak with alpha transparency (see #5185) + oldInstances: [], + maxZ: 0, + events: $.map('focus,mousedown,mouseup,keydown,keypress,click'.split(','), + function(event) { return event + '.dialog-overlay'; }).join(' '), + create: function(dialog) { + if (this.instances.length === 0) { + // prevent use of anchors and inputs + // we use a setTimeout in case the overlay is created from an + // event that we're going to be cancelling (see #2804) + setTimeout(function() { + // handle $(el).dialog().dialog('close') (see #4065) + if ($.ui.dialog.overlay.instances.length) { + $(document).bind($.ui.dialog.overlay.events, function(event) { + // stop events if the z-index of the target is < the z-index of the overlay + // we cannot return true when we don't want to cancel the event (#3523) + if ($(event.target).zIndex() < $.ui.dialog.overlay.maxZ) { + return false; + } + }); + } + }, 1); + + // allow closing by pressing the escape key + $(document).bind('keydown.dialog-overlay', function(event) { + if (dialog.options.closeOnEscape && event.keyCode && + event.keyCode === $.ui.keyCode.ESCAPE) { + + dialog.close(event); + event.preventDefault(); + } + }); + + // handle window resize + $(window).bind('resize.dialog-overlay', $.ui.dialog.overlay.resize); + } + + var $el = (this.oldInstances.pop() || $('
      ').addClass('ui-widget-overlay')) + .appendTo(document.body) + .css({ + width: this.width(), + height: this.height() + }); + + if ($.fn.bgiframe) { + $el.bgiframe(); + } + + this.instances.push($el); + return $el; + }, + + destroy: function($el) { + var indexOf = $.inArray($el, this.instances); + if (indexOf != -1){ + this.oldInstances.push(this.instances.splice(indexOf, 1)[0]); + } + + if (this.instances.length === 0) { + $([document, window]).unbind('.dialog-overlay'); + } + + $el.remove(); + + // adjust the maxZ to allow other modal dialogs to continue to work (see #4309) + var maxZ = 0; + $.each(this.instances, function() { + maxZ = Math.max(maxZ, this.css('z-index')); + }); + this.maxZ = maxZ; + }, + + height: function() { + var scrollHeight, + offsetHeight; + // handle IE 6 + if ($.browser.msie && $.browser.version < 7) { + scrollHeight = Math.max( + document.documentElement.scrollHeight, + document.body.scrollHeight + ); + offsetHeight = Math.max( + document.documentElement.offsetHeight, + document.body.offsetHeight + ); + + if (scrollHeight < offsetHeight) { + return $(window).height() + 'px'; + } else { + return scrollHeight + 'px'; + } + // handle "good" browsers + } else { + return $(document).height() + 'px'; + } + }, + + width: function() { + var scrollWidth, + offsetWidth; + // handle IE 6 + if ($.browser.msie && $.browser.version < 7) { + scrollWidth = Math.max( + document.documentElement.scrollWidth, + document.body.scrollWidth + ); + offsetWidth = Math.max( + document.documentElement.offsetWidth, + document.body.offsetWidth + ); + + if (scrollWidth < offsetWidth) { + return $(window).width() + 'px'; + } else { + return scrollWidth + 'px'; + } + // handle "good" browsers + } else { + return $(document).width() + 'px'; + } + }, + + resize: function() { + /* If the dialog is draggable and the user drags it past the + * right edge of the window, the document becomes wider so we + * need to stretch the overlay. If the user then drags the + * dialog back to the left, the document will become narrower, + * so we need to shrink the overlay to the appropriate size. + * This is handled by shrinking the overlay before setting it + * to the full document size. + */ + var $overlays = $([]); + $.each($.ui.dialog.overlay.instances, function() { + $overlays = $overlays.add(this); + }); + + $overlays.css({ + width: 0, + height: 0 + }).css({ + width: $.ui.dialog.overlay.width(), + height: $.ui.dialog.overlay.height() + }); + } +}); + +$.extend($.ui.dialog.overlay.prototype, { + destroy: function() { + $.ui.dialog.overlay.destroy(this.$el); + } +}); + +}(jQuery)); +/* + * jQuery UI Position 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Position + */ +(function( $, undefined ) { + +$.ui = $.ui || {}; + +var horizontalPositions = /left|center|right/, + verticalPositions = /top|center|bottom/, + center = "center", + _position = $.fn.position, + _offset = $.fn.offset; + +$.fn.position = function( options ) { + if ( !options || !options.of ) { + return _position.apply( this, arguments ); + } + + // make a copy, we don't want to modify arguments + options = $.extend( {}, options ); + + var target = $( options.of ), + targetElem = target[0], + collision = ( options.collision || "flip" ).split( " " ), + offset = options.offset ? options.offset.split( " " ) : [ 0, 0 ], + targetWidth, + targetHeight, + basePosition; + + if ( targetElem.nodeType === 9 ) { + targetWidth = target.width(); + targetHeight = target.height(); + basePosition = { top: 0, left: 0 }; + // TODO: use $.isWindow() in 1.9 + } else if ( targetElem.setTimeout ) { + targetWidth = target.width(); + targetHeight = target.height(); + basePosition = { top: target.scrollTop(), left: target.scrollLeft() }; + } else if ( targetElem.preventDefault ) { + // force left top to allow flipping + options.at = "left top"; + targetWidth = targetHeight = 0; + basePosition = { top: options.of.pageY, left: options.of.pageX }; + } else { + targetWidth = target.outerWidth(); + targetHeight = target.outerHeight(); + basePosition = target.offset(); + } + + // force my and at to have valid horizontal and veritcal positions + // if a value is missing or invalid, it will be converted to center + $.each( [ "my", "at" ], function() { + var pos = ( options[this] || "" ).split( " " ); + if ( pos.length === 1) { + pos = horizontalPositions.test( pos[0] ) ? + pos.concat( [center] ) : + verticalPositions.test( pos[0] ) ? + [ center ].concat( pos ) : + [ center, center ]; + } + pos[ 0 ] = horizontalPositions.test( pos[0] ) ? pos[ 0 ] : center; + pos[ 1 ] = verticalPositions.test( pos[1] ) ? pos[ 1 ] : center; + options[ this ] = pos; + }); + + // normalize collision option + if ( collision.length === 1 ) { + collision[ 1 ] = collision[ 0 ]; + } + + // normalize offset option + offset[ 0 ] = parseInt( offset[0], 10 ) || 0; + if ( offset.length === 1 ) { + offset[ 1 ] = offset[ 0 ]; + } + offset[ 1 ] = parseInt( offset[1], 10 ) || 0; + + if ( options.at[0] === "right" ) { + basePosition.left += targetWidth; + } else if ( options.at[0] === center ) { + basePosition.left += targetWidth / 2; + } + + if ( options.at[1] === "bottom" ) { + basePosition.top += targetHeight; + } else if ( options.at[1] === center ) { + basePosition.top += targetHeight / 2; + } + + basePosition.left += offset[ 0 ]; + basePosition.top += offset[ 1 ]; + + return this.each(function() { + var elem = $( this ), + elemWidth = elem.outerWidth(), + elemHeight = elem.outerHeight(), + marginLeft = parseInt( $.curCSS( this, "marginLeft", true ) ) || 0, + marginTop = parseInt( $.curCSS( this, "marginTop", true ) ) || 0, + collisionWidth = elemWidth + marginLeft + + ( parseInt( $.curCSS( this, "marginRight", true ) ) || 0 ), + collisionHeight = elemHeight + marginTop + + ( parseInt( $.curCSS( this, "marginBottom", true ) ) || 0 ), + position = $.extend( {}, basePosition ), + collisionPosition; + + if ( options.my[0] === "right" ) { + position.left -= elemWidth; + } else if ( options.my[0] === center ) { + position.left -= elemWidth / 2; + } + + if ( options.my[1] === "bottom" ) { + position.top -= elemHeight; + } else if ( options.my[1] === center ) { + position.top -= elemHeight / 2; + } + + // prevent fractions (see #5280) + position.left = Math.round( position.left ); + position.top = Math.round( position.top ); + + collisionPosition = { + left: position.left - marginLeft, + top: position.top - marginTop + }; + + $.each( [ "left", "top" ], function( i, dir ) { + if ( $.ui.position[ collision[i] ] ) { + $.ui.position[ collision[i] ][ dir ]( position, { + targetWidth: targetWidth, + targetHeight: targetHeight, + elemWidth: elemWidth, + elemHeight: elemHeight, + collisionPosition: collisionPosition, + collisionWidth: collisionWidth, + collisionHeight: collisionHeight, + offset: offset, + my: options.my, + at: options.at + }); + } + }); + + if ( $.fn.bgiframe ) { + elem.bgiframe(); + } + elem.offset( $.extend( position, { using: options.using } ) ); + }); +}; + +$.ui.position = { + fit: { + left: function( position, data ) { + var win = $( window ), + over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft(); + position.left = over > 0 ? position.left - over : Math.max( position.left - data.collisionPosition.left, position.left ); + }, + top: function( position, data ) { + var win = $( window ), + over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop(); + position.top = over > 0 ? position.top - over : Math.max( position.top - data.collisionPosition.top, position.top ); + } + }, + + flip: { + left: function( position, data ) { + if ( data.at[0] === center ) { + return; + } + var win = $( window ), + over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft(), + myOffset = data.my[ 0 ] === "left" ? + -data.elemWidth : + data.my[ 0 ] === "right" ? + data.elemWidth : + 0, + atOffset = data.at[ 0 ] === "left" ? + data.targetWidth : + -data.targetWidth, + offset = -2 * data.offset[ 0 ]; + position.left += data.collisionPosition.left < 0 ? + myOffset + atOffset + offset : + over > 0 ? + myOffset + atOffset + offset : + 0; + }, + top: function( position, data ) { + if ( data.at[1] === center ) { + return; + } + var win = $( window ), + over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop(), + myOffset = data.my[ 1 ] === "top" ? + -data.elemHeight : + data.my[ 1 ] === "bottom" ? + data.elemHeight : + 0, + atOffset = data.at[ 1 ] === "top" ? + data.targetHeight : + -data.targetHeight, + offset = -2 * data.offset[ 1 ]; + position.top += data.collisionPosition.top < 0 ? + myOffset + atOffset + offset : + over > 0 ? + myOffset + atOffset + offset : + 0; + } + } +}; + +// offset setter from jQuery 1.4 +if ( !$.offset.setOffset ) { + $.offset.setOffset = function( elem, options ) { + // set position first, in-case top/left are set even on static elem + if ( /static/.test( $.curCSS( elem, "position" ) ) ) { + elem.style.position = "relative"; + } + var curElem = $( elem ), + curOffset = curElem.offset(), + curTop = parseInt( $.curCSS( elem, "top", true ), 10 ) || 0, + curLeft = parseInt( $.curCSS( elem, "left", true ), 10) || 0, + props = { + top: (options.top - curOffset.top) + curTop, + left: (options.left - curOffset.left) + curLeft + }; + + if ( 'using' in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + }; + + $.fn.offset = function( options ) { + var elem = this[ 0 ]; + if ( !elem || !elem.ownerDocument ) { return null; } + if ( options ) { + return this.each(function() { + $.offset.setOffset( this, options ); + }); + } + return _offset.call( this ); + }; +} + +}( jQuery )); +/* + * jQuery UI Progressbar 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +$.widget( "ui.progressbar", { + options: { + value: 0, + max: 100 + }, + + min: 0, + + _create: function() { + this.element + .addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" ) + .attr({ + role: "progressbar", + "aria-valuemin": this.min, + "aria-valuemax": this.options.max, + "aria-valuenow": this._value() + }); + + this.valueDiv = $( "
      " ) + .appendTo( this.element ); + + this.oldValue = this._value(); + this._refreshValue(); + }, + + destroy: function() { + this.element + .removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" ) + .removeAttr( "role" ) + .removeAttr( "aria-valuemin" ) + .removeAttr( "aria-valuemax" ) + .removeAttr( "aria-valuenow" ); + + this.valueDiv.remove(); + + $.Widget.prototype.destroy.apply( this, arguments ); + }, + + value: function( newValue ) { + if ( newValue === undefined ) { + return this._value(); + } + + this._setOption( "value", newValue ); + return this; + }, + + _setOption: function( key, value ) { + if ( key === "value" ) { + this.options.value = value; + this._refreshValue(); + if ( this._value() === this.options.max ) { + this._trigger( "complete" ); + } + } + + $.Widget.prototype._setOption.apply( this, arguments ); + }, + + _value: function() { + var val = this.options.value; + // normalize invalid value + if ( typeof val !== "number" ) { + val = 0; + } + return Math.min( this.options.max, Math.max( this.min, val ) ); + }, + + _percentage: function() { + return 100 * this._value() / this.options.max; + }, + + _refreshValue: function() { + var value = this.value(); + var percentage = this._percentage(); + + if ( this.oldValue !== value ) { + this.oldValue = value; + this._trigger( "change" ); + } + + this.valueDiv + .toggleClass( "ui-corner-right", value === this.options.max ) + .width( percentage.toFixed(0) + "%" ); + this.element.attr( "aria-valuenow", value ); + } +}); + +$.extend( $.ui.progressbar, { + version: "1.8.11" +}); + +})( jQuery ); +/* + * jQuery UI Slider 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +// number of pages in a slider +// (how many times can you page up/down to go through the whole range) +var numPages = 5; + +$.widget( "ui.slider", $.ui.mouse, { + + widgetEventPrefix: "slide", + + options: { + animate: false, + distance: 0, + max: 100, + min: 0, + orientation: "horizontal", + range: false, + step: 1, + value: 0, + values: null + }, + + _create: function() { + var self = this, + o = this.options; + + this._keySliding = false; + this._mouseSliding = false; + this._animateOff = true; + this._handleIndex = null; + this._detectOrientation(); + this._mouseInit(); + + this.element + .addClass( "ui-slider" + + " ui-slider-" + this.orientation + + " ui-widget" + + " ui-widget-content" + + " ui-corner-all" ); + + if ( o.disabled ) { + this.element.addClass( "ui-slider-disabled ui-disabled" ); + } + + this.range = $([]); + + if ( o.range ) { + if ( o.range === true ) { + this.range = $( "
      " ); + if ( !o.values ) { + o.values = [ this._valueMin(), this._valueMin() ]; + } + if ( o.values.length && o.values.length !== 2 ) { + o.values = [ o.values[0], o.values[0] ]; + } + } else { + this.range = $( "
      " ); + } + + this.range + .appendTo( this.element ) + .addClass( "ui-slider-range" ); + + if ( o.range === "min" || o.range === "max" ) { + this.range.addClass( "ui-slider-range-" + o.range ); + } + + // note: this isn't the most fittingly semantic framework class for this element, + // but worked best visually with a variety of themes + this.range.addClass( "ui-widget-header" ); + } + + if ( $( ".ui-slider-handle", this.element ).length === 0 ) { + $( "" ) + .appendTo( this.element ) + .addClass( "ui-slider-handle" ); + } + + if ( o.values && o.values.length ) { + while ( $(".ui-slider-handle", this.element).length < o.values.length ) { + $( "" ) + .appendTo( this.element ) + .addClass( "ui-slider-handle" ); + } + } + + this.handles = $( ".ui-slider-handle", this.element ) + .addClass( "ui-state-default" + + " ui-corner-all" ); + + this.handle = this.handles.eq( 0 ); + + this.handles.add( this.range ).filter( "a" ) + .click(function( event ) { + event.preventDefault(); + }) + .hover(function() { + if ( !o.disabled ) { + $( this ).addClass( "ui-state-hover" ); + } + }, function() { + $( this ).removeClass( "ui-state-hover" ); + }) + .focus(function() { + if ( !o.disabled ) { + $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" ); + $( this ).addClass( "ui-state-focus" ); + } else { + $( this ).blur(); + } + }) + .blur(function() { + $( this ).removeClass( "ui-state-focus" ); + }); + + this.handles.each(function( i ) { + $( this ).data( "index.ui-slider-handle", i ); + }); + + this.handles + .keydown(function( event ) { + var ret = true, + index = $( this ).data( "index.ui-slider-handle" ), + allowed, + curVal, + newVal, + step; + + if ( self.options.disabled ) { + return; + } + + switch ( event.keyCode ) { + case $.ui.keyCode.HOME: + case $.ui.keyCode.END: + case $.ui.keyCode.PAGE_UP: + case $.ui.keyCode.PAGE_DOWN: + case $.ui.keyCode.UP: + case $.ui.keyCode.RIGHT: + case $.ui.keyCode.DOWN: + case $.ui.keyCode.LEFT: + ret = false; + if ( !self._keySliding ) { + self._keySliding = true; + $( this ).addClass( "ui-state-active" ); + allowed = self._start( event, index ); + if ( allowed === false ) { + return; + } + } + break; + } + + step = self.options.step; + if ( self.options.values && self.options.values.length ) { + curVal = newVal = self.values( index ); + } else { + curVal = newVal = self.value(); + } + + switch ( event.keyCode ) { + case $.ui.keyCode.HOME: + newVal = self._valueMin(); + break; + case $.ui.keyCode.END: + newVal = self._valueMax(); + break; + case $.ui.keyCode.PAGE_UP: + newVal = self._trimAlignValue( curVal + ( (self._valueMax() - self._valueMin()) / numPages ) ); + break; + case $.ui.keyCode.PAGE_DOWN: + newVal = self._trimAlignValue( curVal - ( (self._valueMax() - self._valueMin()) / numPages ) ); + break; + case $.ui.keyCode.UP: + case $.ui.keyCode.RIGHT: + if ( curVal === self._valueMax() ) { + return; + } + newVal = self._trimAlignValue( curVal + step ); + break; + case $.ui.keyCode.DOWN: + case $.ui.keyCode.LEFT: + if ( curVal === self._valueMin() ) { + return; + } + newVal = self._trimAlignValue( curVal - step ); + break; + } + + self._slide( event, index, newVal ); + + return ret; + + }) + .keyup(function( event ) { + var index = $( this ).data( "index.ui-slider-handle" ); + + if ( self._keySliding ) { + self._keySliding = false; + self._stop( event, index ); + self._change( event, index ); + $( this ).removeClass( "ui-state-active" ); + } + + }); + + this._refreshValue(); + + this._animateOff = false; + }, + + destroy: function() { + this.handles.remove(); + this.range.remove(); + + this.element + .removeClass( "ui-slider" + + " ui-slider-horizontal" + + " ui-slider-vertical" + + " ui-slider-disabled" + + " ui-widget" + + " ui-widget-content" + + " ui-corner-all" ) + .removeData( "slider" ) + .unbind( ".slider" ); + + this._mouseDestroy(); + + return this; + }, + + _mouseCapture: function( event ) { + var o = this.options, + position, + normValue, + distance, + closestHandle, + self, + index, + allowed, + offset, + mouseOverHandle; + + if ( o.disabled ) { + return false; + } + + this.elementSize = { + width: this.element.outerWidth(), + height: this.element.outerHeight() + }; + this.elementOffset = this.element.offset(); + + position = { x: event.pageX, y: event.pageY }; + normValue = this._normValueFromMouse( position ); + distance = this._valueMax() - this._valueMin() + 1; + self = this; + this.handles.each(function( i ) { + var thisDistance = Math.abs( normValue - self.values(i) ); + if ( distance > thisDistance ) { + distance = thisDistance; + closestHandle = $( this ); + index = i; + } + }); + + // workaround for bug #3736 (if both handles of a range are at 0, + // the first is always used as the one with least distance, + // and moving it is obviously prevented by preventing negative ranges) + if( o.range === true && this.values(1) === o.min ) { + index += 1; + closestHandle = $( this.handles[index] ); + } + + allowed = this._start( event, index ); + if ( allowed === false ) { + return false; + } + this._mouseSliding = true; + + self._handleIndex = index; + + closestHandle + .addClass( "ui-state-active" ) + .focus(); + + offset = closestHandle.offset(); + mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" ); + this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : { + left: event.pageX - offset.left - ( closestHandle.width() / 2 ), + top: event.pageY - offset.top - + ( closestHandle.height() / 2 ) - + ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) - + ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) + + ( parseInt( closestHandle.css("marginTop"), 10 ) || 0) + }; + + if ( !this.handles.hasClass( "ui-state-hover" ) ) { + this._slide( event, index, normValue ); + } + this._animateOff = true; + return true; + }, + + _mouseStart: function( event ) { + return true; + }, + + _mouseDrag: function( event ) { + var position = { x: event.pageX, y: event.pageY }, + normValue = this._normValueFromMouse( position ); + + this._slide( event, this._handleIndex, normValue ); + + return false; + }, + + _mouseStop: function( event ) { + this.handles.removeClass( "ui-state-active" ); + this._mouseSliding = false; + + this._stop( event, this._handleIndex ); + this._change( event, this._handleIndex ); + + this._handleIndex = null; + this._clickOffset = null; + this._animateOff = false; + + return false; + }, + + _detectOrientation: function() { + this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal"; + }, + + _normValueFromMouse: function( position ) { + var pixelTotal, + pixelMouse, + percentMouse, + valueTotal, + valueMouse; + + if ( this.orientation === "horizontal" ) { + pixelTotal = this.elementSize.width; + pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 ); + } else { + pixelTotal = this.elementSize.height; + pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 ); + } + + percentMouse = ( pixelMouse / pixelTotal ); + if ( percentMouse > 1 ) { + percentMouse = 1; + } + if ( percentMouse < 0 ) { + percentMouse = 0; + } + if ( this.orientation === "vertical" ) { + percentMouse = 1 - percentMouse; + } + + valueTotal = this._valueMax() - this._valueMin(); + valueMouse = this._valueMin() + percentMouse * valueTotal; + + return this._trimAlignValue( valueMouse ); + }, + + _start: function( event, index ) { + var uiHash = { + handle: this.handles[ index ], + value: this.value() + }; + if ( this.options.values && this.options.values.length ) { + uiHash.value = this.values( index ); + uiHash.values = this.values(); + } + return this._trigger( "start", event, uiHash ); + }, + + _slide: function( event, index, newVal ) { + var otherVal, + newValues, + allowed; + + if ( this.options.values && this.options.values.length ) { + otherVal = this.values( index ? 0 : 1 ); + + if ( ( this.options.values.length === 2 && this.options.range === true ) && + ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) ) + ) { + newVal = otherVal; + } + + if ( newVal !== this.values( index ) ) { + newValues = this.values(); + newValues[ index ] = newVal; + // A slide can be canceled by returning false from the slide callback + allowed = this._trigger( "slide", event, { + handle: this.handles[ index ], + value: newVal, + values: newValues + } ); + otherVal = this.values( index ? 0 : 1 ); + if ( allowed !== false ) { + this.values( index, newVal, true ); + } + } + } else { + if ( newVal !== this.value() ) { + // A slide can be canceled by returning false from the slide callback + allowed = this._trigger( "slide", event, { + handle: this.handles[ index ], + value: newVal + } ); + if ( allowed !== false ) { + this.value( newVal ); + } + } + } + }, + + _stop: function( event, index ) { + var uiHash = { + handle: this.handles[ index ], + value: this.value() + }; + if ( this.options.values && this.options.values.length ) { + uiHash.value = this.values( index ); + uiHash.values = this.values(); + } + + this._trigger( "stop", event, uiHash ); + }, + + _change: function( event, index ) { + if ( !this._keySliding && !this._mouseSliding ) { + var uiHash = { + handle: this.handles[ index ], + value: this.value() + }; + if ( this.options.values && this.options.values.length ) { + uiHash.value = this.values( index ); + uiHash.values = this.values(); + } + + this._trigger( "change", event, uiHash ); + } + }, + + value: function( newValue ) { + if ( arguments.length ) { + this.options.value = this._trimAlignValue( newValue ); + this._refreshValue(); + this._change( null, 0 ); + } + + return this._value(); + }, + + values: function( index, newValue ) { + var vals, + newValues, + i; + + if ( arguments.length > 1 ) { + this.options.values[ index ] = this._trimAlignValue( newValue ); + this._refreshValue(); + this._change( null, index ); + } + + if ( arguments.length ) { + if ( $.isArray( arguments[ 0 ] ) ) { + vals = this.options.values; + newValues = arguments[ 0 ]; + for ( i = 0; i < vals.length; i += 1 ) { + vals[ i ] = this._trimAlignValue( newValues[ i ] ); + this._change( null, i ); + } + this._refreshValue(); + } else { + if ( this.options.values && this.options.values.length ) { + return this._values( index ); + } else { + return this.value(); + } + } + } else { + return this._values(); + } + }, + + _setOption: function( key, value ) { + var i, + valsLength = 0; + + if ( $.isArray( this.options.values ) ) { + valsLength = this.options.values.length; + } + + $.Widget.prototype._setOption.apply( this, arguments ); + + switch ( key ) { + case "disabled": + if ( value ) { + this.handles.filter( ".ui-state-focus" ).blur(); + this.handles.removeClass( "ui-state-hover" ); + this.handles.attr( "disabled", "disabled" ); + this.element.addClass( "ui-disabled" ); + } else { + this.handles.removeAttr( "disabled" ); + this.element.removeClass( "ui-disabled" ); + } + break; + case "orientation": + this._detectOrientation(); + this.element + .removeClass( "ui-slider-horizontal ui-slider-vertical" ) + .addClass( "ui-slider-" + this.orientation ); + this._refreshValue(); + break; + case "value": + this._animateOff = true; + this._refreshValue(); + this._change( null, 0 ); + this._animateOff = false; + break; + case "values": + this._animateOff = true; + this._refreshValue(); + for ( i = 0; i < valsLength; i += 1 ) { + this._change( null, i ); + } + this._animateOff = false; + break; + } + }, + + //internal value getter + // _value() returns value trimmed by min and max, aligned by step + _value: function() { + var val = this.options.value; + val = this._trimAlignValue( val ); + + return val; + }, + + //internal values getter + // _values() returns array of values trimmed by min and max, aligned by step + // _values( index ) returns single value trimmed by min and max, aligned by step + _values: function( index ) { + var val, + vals, + i; + + if ( arguments.length ) { + val = this.options.values[ index ]; + val = this._trimAlignValue( val ); + + return val; + } else { + // .slice() creates a copy of the array + // this copy gets trimmed by min and max and then returned + vals = this.options.values.slice(); + for ( i = 0; i < vals.length; i+= 1) { + vals[ i ] = this._trimAlignValue( vals[ i ] ); + } + + return vals; + } + }, + + // returns the step-aligned value that val is closest to, between (inclusive) min and max + _trimAlignValue: function( val ) { + if ( val <= this._valueMin() ) { + return this._valueMin(); + } + if ( val >= this._valueMax() ) { + return this._valueMax(); + } + var step = ( this.options.step > 0 ) ? this.options.step : 1, + valModStep = (val - this._valueMin()) % step; + alignValue = val - valModStep; + + if ( Math.abs(valModStep) * 2 >= step ) { + alignValue += ( valModStep > 0 ) ? step : ( -step ); + } + + // Since JavaScript has problems with large floats, round + // the final value to 5 digits after the decimal point (see #4124) + return parseFloat( alignValue.toFixed(5) ); + }, + + _valueMin: function() { + return this.options.min; + }, + + _valueMax: function() { + return this.options.max; + }, + + _refreshValue: function() { + var oRange = this.options.range, + o = this.options, + self = this, + animate = ( !this._animateOff ) ? o.animate : false, + valPercent, + _set = {}, + lastValPercent, + value, + valueMin, + valueMax; + + if ( this.options.values && this.options.values.length ) { + this.handles.each(function( i, j ) { + valPercent = ( self.values(i) - self._valueMin() ) / ( self._valueMax() - self._valueMin() ) * 100; + _set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; + $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); + if ( self.options.range === true ) { + if ( self.orientation === "horizontal" ) { + if ( i === 0 ) { + self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate ); + } + if ( i === 1 ) { + self.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } ); + } + } else { + if ( i === 0 ) { + self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate ); + } + if ( i === 1 ) { + self.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } ); + } + } + } + lastValPercent = valPercent; + }); + } else { + value = this.value(); + valueMin = this._valueMin(); + valueMax = this._valueMax(); + valPercent = ( valueMax !== valueMin ) ? + ( value - valueMin ) / ( valueMax - valueMin ) * 100 : + 0; + _set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; + this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); + + if ( oRange === "min" && this.orientation === "horizontal" ) { + this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate ); + } + if ( oRange === "max" && this.orientation === "horizontal" ) { + this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } ); + } + if ( oRange === "min" && this.orientation === "vertical" ) { + this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate ); + } + if ( oRange === "max" && this.orientation === "vertical" ) { + this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } ); + } + } + } + +}); + +$.extend( $.ui.slider, { + version: "1.8.11" +}); + +}(jQuery)); +/* + * jQuery UI Tabs 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +var tabId = 0, + listId = 0; + +function getNextTabId() { + return ++tabId; +} + +function getNextListId() { + return ++listId; +} + +$.widget( "ui.tabs", { + options: { + add: null, + ajaxOptions: null, + cache: false, + cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true } + collapsible: false, + disable: null, + disabled: [], + enable: null, + event: "click", + fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 } + idPrefix: "ui-tabs-", + load: null, + panelTemplate: "
      ", + remove: null, + select: null, + show: null, + spinner: "Loading…", + tabTemplate: "
    • #{label}
    • " + }, + + _create: function() { + this._tabify( true ); + }, + + _setOption: function( key, value ) { + if ( key == "selected" ) { + if (this.options.collapsible && value == this.options.selected ) { + return; + } + this.select( value ); + } else { + this.options[ key ] = value; + this._tabify(); + } + }, + + _tabId: function( a ) { + return a.title && a.title.replace( /\s/g, "_" ).replace( /[^\w\u00c0-\uFFFF-]/g, "" ) || + this.options.idPrefix + getNextTabId(); + }, + + _sanitizeSelector: function( hash ) { + // we need this because an id may contain a ":" + return hash.replace( /:/g, "\\:" ); + }, + + _cookie: function() { + var cookie = this.cookie || + ( this.cookie = this.options.cookie.name || "ui-tabs-" + getNextListId() ); + return $.cookie.apply( null, [ cookie ].concat( $.makeArray( arguments ) ) ); + }, + + _ui: function( tab, panel ) { + return { + tab: tab, + panel: panel, + index: this.anchors.index( tab ) + }; + }, + + _cleanup: function() { + // restore all former loading tabs labels + this.lis.filter( ".ui-state-processing" ) + .removeClass( "ui-state-processing" ) + .find( "span:data(label.tabs)" ) + .each(function() { + var el = $( this ); + el.html( el.data( "label.tabs" ) ).removeData( "label.tabs" ); + }); + }, + + _tabify: function( init ) { + var self = this, + o = this.options, + fragmentId = /^#.+/; // Safari 2 reports '#' for an empty hash + + this.list = this.element.find( "ol,ul" ).eq( 0 ); + this.lis = $( " > li:has(a[href])", this.list ); + this.anchors = this.lis.map(function() { + return $( "a", this )[ 0 ]; + }); + this.panels = $( [] ); + + this.anchors.each(function( i, a ) { + var href = $( a ).attr( "href" ); + // For dynamically created HTML that contains a hash as href IE < 8 expands + // such href to the full page url with hash and then misinterprets tab as ajax. + // Same consideration applies for an added tab with a fragment identifier + // since a[href=#fragment-identifier] does unexpectedly not match. + // Thus normalize href attribute... + var hrefBase = href.split( "#" )[ 0 ], + baseEl; + if ( hrefBase && ( hrefBase === location.toString().split( "#" )[ 0 ] || + ( baseEl = $( "base" )[ 0 ]) && hrefBase === baseEl.href ) ) { + href = a.hash; + a.href = href; + } + + // inline tab + if ( fragmentId.test( href ) ) { + self.panels = self.panels.add( self.element.find( self._sanitizeSelector( href ) ) ); + // remote tab + // prevent loading the page itself if href is just "#" + } else if ( href && href !== "#" ) { + // required for restore on destroy + $.data( a, "href.tabs", href ); + + // TODO until #3808 is fixed strip fragment identifier from url + // (IE fails to load from such url) + $.data( a, "load.tabs", href.replace( /#.*$/, "" ) ); + + var id = self._tabId( a ); + a.href = "#" + id; + var $panel = self.element.find( "#" + id ); + if ( !$panel.length ) { + $panel = $( o.panelTemplate ) + .attr( "id", id ) + .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) + .insertAfter( self.panels[ i - 1 ] || self.list ); + $panel.data( "destroy.tabs", true ); + } + self.panels = self.panels.add( $panel ); + // invalid tab href + } else { + o.disabled.push( i ); + } + }); + + // initialization from scratch + if ( init ) { + // attach necessary classes for styling + this.element.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" ); + this.list.addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ); + this.lis.addClass( "ui-state-default ui-corner-top" ); + this.panels.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ); + + // Selected tab + // use "selected" option or try to retrieve: + // 1. from fragment identifier in url + // 2. from cookie + // 3. from selected class attribute on
    • + if ( o.selected === undefined ) { + if ( location.hash ) { + this.anchors.each(function( i, a ) { + if ( a.hash == location.hash ) { + o.selected = i; + return false; + } + }); + } + if ( typeof o.selected !== "number" && o.cookie ) { + o.selected = parseInt( self._cookie(), 10 ); + } + if ( typeof o.selected !== "number" && this.lis.filter( ".ui-tabs-selected" ).length ) { + o.selected = this.lis.index( this.lis.filter( ".ui-tabs-selected" ) ); + } + o.selected = o.selected || ( this.lis.length ? 0 : -1 ); + } else if ( o.selected === null ) { // usage of null is deprecated, TODO remove in next release + o.selected = -1; + } + + // sanity check - default to first tab... + o.selected = ( ( o.selected >= 0 && this.anchors[ o.selected ] ) || o.selected < 0 ) + ? o.selected + : 0; + + // Take disabling tabs via class attribute from HTML + // into account and update option properly. + // A selected tab cannot become disabled. + o.disabled = $.unique( o.disabled.concat( + $.map( this.lis.filter( ".ui-state-disabled" ), function( n, i ) { + return self.lis.index( n ); + }) + ) ).sort(); + + if ( $.inArray( o.selected, o.disabled ) != -1 ) { + o.disabled.splice( $.inArray( o.selected, o.disabled ), 1 ); + } + + // highlight selected tab + this.panels.addClass( "ui-tabs-hide" ); + this.lis.removeClass( "ui-tabs-selected ui-state-active" ); + // check for length avoids error when initializing empty list + if ( o.selected >= 0 && this.anchors.length ) { + self.element.find( self._sanitizeSelector( self.anchors[ o.selected ].hash ) ).removeClass( "ui-tabs-hide" ); + this.lis.eq( o.selected ).addClass( "ui-tabs-selected ui-state-active" ); + + // seems to be expected behavior that the show callback is fired + self.element.queue( "tabs", function() { + self._trigger( "show", null, + self._ui( self.anchors[ o.selected ], self.element.find( self._sanitizeSelector( self.anchors[ o.selected ].hash ) )[ 0 ] ) ); + }); + + this.load( o.selected ); + } + + // clean up to avoid memory leaks in certain versions of IE 6 + // TODO: namespace this event + $( window ).bind( "unload", function() { + self.lis.add( self.anchors ).unbind( ".tabs" ); + self.lis = self.anchors = self.panels = null; + }); + // update selected after add/remove + } else { + o.selected = this.lis.index( this.lis.filter( ".ui-tabs-selected" ) ); + } + + // update collapsible + // TODO: use .toggleClass() + this.element[ o.collapsible ? "addClass" : "removeClass" ]( "ui-tabs-collapsible" ); + + // set or update cookie after init and add/remove respectively + if ( o.cookie ) { + this._cookie( o.selected, o.cookie ); + } + + // disable tabs + for ( var i = 0, li; ( li = this.lis[ i ] ); i++ ) { + $( li )[ $.inArray( i, o.disabled ) != -1 && + // TODO: use .toggleClass() + !$( li ).hasClass( "ui-tabs-selected" ) ? "addClass" : "removeClass" ]( "ui-state-disabled" ); + } + + // reset cache if switching from cached to not cached + if ( o.cache === false ) { + this.anchors.removeData( "cache.tabs" ); + } + + // remove all handlers before, tabify may run on existing tabs after add or option change + this.lis.add( this.anchors ).unbind( ".tabs" ); + + if ( o.event !== "mouseover" ) { + var addState = function( state, el ) { + if ( el.is( ":not(.ui-state-disabled)" ) ) { + el.addClass( "ui-state-" + state ); + } + }; + var removeState = function( state, el ) { + el.removeClass( "ui-state-" + state ); + }; + this.lis.bind( "mouseover.tabs" , function() { + addState( "hover", $( this ) ); + }); + this.lis.bind( "mouseout.tabs", function() { + removeState( "hover", $( this ) ); + }); + this.anchors.bind( "focus.tabs", function() { + addState( "focus", $( this ).closest( "li" ) ); + }); + this.anchors.bind( "blur.tabs", function() { + removeState( "focus", $( this ).closest( "li" ) ); + }); + } + + // set up animations + var hideFx, showFx; + if ( o.fx ) { + if ( $.isArray( o.fx ) ) { + hideFx = o.fx[ 0 ]; + showFx = o.fx[ 1 ]; + } else { + hideFx = showFx = o.fx; + } + } + + // Reset certain styles left over from animation + // and prevent IE's ClearType bug... + function resetStyle( $el, fx ) { + $el.css( "display", "" ); + if ( !$.support.opacity && fx.opacity ) { + $el[ 0 ].style.removeAttribute( "filter" ); + } + } + + // Show a tab... + var showTab = showFx + ? function( clicked, $show ) { + $( clicked ).closest( "li" ).addClass( "ui-tabs-selected ui-state-active" ); + $show.hide().removeClass( "ui-tabs-hide" ) // avoid flicker that way + .animate( showFx, showFx.duration || "normal", function() { + resetStyle( $show, showFx ); + self._trigger( "show", null, self._ui( clicked, $show[ 0 ] ) ); + }); + } + : function( clicked, $show ) { + $( clicked ).closest( "li" ).addClass( "ui-tabs-selected ui-state-active" ); + $show.removeClass( "ui-tabs-hide" ); + self._trigger( "show", null, self._ui( clicked, $show[ 0 ] ) ); + }; + + // Hide a tab, $show is optional... + var hideTab = hideFx + ? function( clicked, $hide ) { + $hide.animate( hideFx, hideFx.duration || "normal", function() { + self.lis.removeClass( "ui-tabs-selected ui-state-active" ); + $hide.addClass( "ui-tabs-hide" ); + resetStyle( $hide, hideFx ); + self.element.dequeue( "tabs" ); + }); + } + : function( clicked, $hide, $show ) { + self.lis.removeClass( "ui-tabs-selected ui-state-active" ); + $hide.addClass( "ui-tabs-hide" ); + self.element.dequeue( "tabs" ); + }; + + // attach tab event handler, unbind to avoid duplicates from former tabifying... + this.anchors.bind( o.event + ".tabs", function() { + var el = this, + $li = $(el).closest( "li" ), + $hide = self.panels.filter( ":not(.ui-tabs-hide)" ), + $show = self.element.find( self._sanitizeSelector( el.hash ) ); + + // If tab is already selected and not collapsible or tab disabled or + // or is already loading or click callback returns false stop here. + // Check if click handler returns false last so that it is not executed + // for a disabled or loading tab! + if ( ( $li.hasClass( "ui-tabs-selected" ) && !o.collapsible) || + $li.hasClass( "ui-state-disabled" ) || + $li.hasClass( "ui-state-processing" ) || + self.panels.filter( ":animated" ).length || + self._trigger( "select", null, self._ui( this, $show[ 0 ] ) ) === false ) { + this.blur(); + return false; + } + + o.selected = self.anchors.index( this ); + + self.abort(); + + // if tab may be closed + if ( o.collapsible ) { + if ( $li.hasClass( "ui-tabs-selected" ) ) { + o.selected = -1; + + if ( o.cookie ) { + self._cookie( o.selected, o.cookie ); + } + + self.element.queue( "tabs", function() { + hideTab( el, $hide ); + }).dequeue( "tabs" ); + + this.blur(); + return false; + } else if ( !$hide.length ) { + if ( o.cookie ) { + self._cookie( o.selected, o.cookie ); + } + + self.element.queue( "tabs", function() { + showTab( el, $show ); + }); + + // TODO make passing in node possible, see also http://dev.jqueryui.com/ticket/3171 + self.load( self.anchors.index( this ) ); + + this.blur(); + return false; + } + } + + if ( o.cookie ) { + self._cookie( o.selected, o.cookie ); + } + + // show new tab + if ( $show.length ) { + if ( $hide.length ) { + self.element.queue( "tabs", function() { + hideTab( el, $hide ); + }); + } + self.element.queue( "tabs", function() { + showTab( el, $show ); + }); + + self.load( self.anchors.index( this ) ); + } else { + throw "jQuery UI Tabs: Mismatching fragment identifier."; + } + + // Prevent IE from keeping other link focussed when using the back button + // and remove dotted border from clicked link. This is controlled via CSS + // in modern browsers; blur() removes focus from address bar in Firefox + // which can become a usability and annoying problem with tabs('rotate'). + if ( $.browser.msie ) { + this.blur(); + } + }); + + // disable click in any case + this.anchors.bind( "click.tabs", function(){ + return false; + }); + }, + + _getIndex: function( index ) { + // meta-function to give users option to provide a href string instead of a numerical index. + // also sanitizes numerical indexes to valid values. + if ( typeof index == "string" ) { + index = this.anchors.index( this.anchors.filter( "[href$=" + index + "]" ) ); + } + + return index; + }, + + destroy: function() { + var o = this.options; + + this.abort(); + + this.element + .unbind( ".tabs" ) + .removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" ) + .removeData( "tabs" ); + + this.list.removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ); + + this.anchors.each(function() { + var href = $.data( this, "href.tabs" ); + if ( href ) { + this.href = href; + } + var $this = $( this ).unbind( ".tabs" ); + $.each( [ "href", "load", "cache" ], function( i, prefix ) { + $this.removeData( prefix + ".tabs" ); + }); + }); + + this.lis.unbind( ".tabs" ).add( this.panels ).each(function() { + if ( $.data( this, "destroy.tabs" ) ) { + $( this ).remove(); + } else { + $( this ).removeClass([ + "ui-state-default", + "ui-corner-top", + "ui-tabs-selected", + "ui-state-active", + "ui-state-hover", + "ui-state-focus", + "ui-state-disabled", + "ui-tabs-panel", + "ui-widget-content", + "ui-corner-bottom", + "ui-tabs-hide" + ].join( " " ) ); + } + }); + + if ( o.cookie ) { + this._cookie( null, o.cookie ); + } + + return this; + }, + + add: function( url, label, index ) { + if ( index === undefined ) { + index = this.anchors.length; + } + + var self = this, + o = this.options, + $li = $( o.tabTemplate.replace( /#\{href\}/g, url ).replace( /#\{label\}/g, label ) ), + id = !url.indexOf( "#" ) ? url.replace( "#", "" ) : this._tabId( $( "a", $li )[ 0 ] ); + + $li.addClass( "ui-state-default ui-corner-top" ).data( "destroy.tabs", true ); + + // try to find an existing element before creating a new one + var $panel = self.element.find( "#" + id ); + if ( !$panel.length ) { + $panel = $( o.panelTemplate ) + .attr( "id", id ) + .data( "destroy.tabs", true ); + } + $panel.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide" ); + + if ( index >= this.lis.length ) { + $li.appendTo( this.list ); + $panel.appendTo( this.list[ 0 ].parentNode ); + } else { + $li.insertBefore( this.lis[ index ] ); + $panel.insertBefore( this.panels[ index ] ); + } + + o.disabled = $.map( o.disabled, function( n, i ) { + return n >= index ? ++n : n; + }); + + this._tabify(); + + if ( this.anchors.length == 1 ) { + o.selected = 0; + $li.addClass( "ui-tabs-selected ui-state-active" ); + $panel.removeClass( "ui-tabs-hide" ); + this.element.queue( "tabs", function() { + self._trigger( "show", null, self._ui( self.anchors[ 0 ], self.panels[ 0 ] ) ); + }); + + this.load( 0 ); + } + + this._trigger( "add", null, this._ui( this.anchors[ index ], this.panels[ index ] ) ); + return this; + }, + + remove: function( index ) { + index = this._getIndex( index ); + var o = this.options, + $li = this.lis.eq( index ).remove(), + $panel = this.panels.eq( index ).remove(); + + // If selected tab was removed focus tab to the right or + // in case the last tab was removed the tab to the left. + if ( $li.hasClass( "ui-tabs-selected" ) && this.anchors.length > 1) { + this.select( index + ( index + 1 < this.anchors.length ? 1 : -1 ) ); + } + + o.disabled = $.map( + $.grep( o.disabled, function(n, i) { + return n != index; + }), + function( n, i ) { + return n >= index ? --n : n; + }); + + this._tabify(); + + this._trigger( "remove", null, this._ui( $li.find( "a" )[ 0 ], $panel[ 0 ] ) ); + return this; + }, + + enable: function( index ) { + index = this._getIndex( index ); + var o = this.options; + if ( $.inArray( index, o.disabled ) == -1 ) { + return; + } + + this.lis.eq( index ).removeClass( "ui-state-disabled" ); + o.disabled = $.grep( o.disabled, function( n, i ) { + return n != index; + }); + + this._trigger( "enable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) ); + return this; + }, + + disable: function( index ) { + index = this._getIndex( index ); + var self = this, o = this.options; + // cannot disable already selected tab + if ( index != o.selected ) { + this.lis.eq( index ).addClass( "ui-state-disabled" ); + + o.disabled.push( index ); + o.disabled.sort(); + + this._trigger( "disable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) ); + } + + return this; + }, + + select: function( index ) { + index = this._getIndex( index ); + if ( index == -1 ) { + if ( this.options.collapsible && this.options.selected != -1 ) { + index = this.options.selected; + } else { + return this; + } + } + this.anchors.eq( index ).trigger( this.options.event + ".tabs" ); + return this; + }, + + load: function( index ) { + index = this._getIndex( index ); + var self = this, + o = this.options, + a = this.anchors.eq( index )[ 0 ], + url = $.data( a, "load.tabs" ); + + this.abort(); + + // not remote or from cache + if ( !url || this.element.queue( "tabs" ).length !== 0 && $.data( a, "cache.tabs" ) ) { + this.element.dequeue( "tabs" ); + return; + } + + // load remote from here on + this.lis.eq( index ).addClass( "ui-state-processing" ); + + if ( o.spinner ) { + var span = $( "span", a ); + span.data( "label.tabs", span.html() ).html( o.spinner ); + } + + this.xhr = $.ajax( $.extend( {}, o.ajaxOptions, { + url: url, + success: function( r, s ) { + self.element.find( self._sanitizeSelector( a.hash ) ).html( r ); + + // take care of tab labels + self._cleanup(); + + if ( o.cache ) { + $.data( a, "cache.tabs", true ); + } + + self._trigger( "load", null, self._ui( self.anchors[ index ], self.panels[ index ] ) ); + try { + o.ajaxOptions.success( r, s ); + } + catch ( e ) {} + }, + error: function( xhr, s, e ) { + // take care of tab labels + self._cleanup(); + + self._trigger( "load", null, self._ui( self.anchors[ index ], self.panels[ index ] ) ); + try { + // Passing index avoid a race condition when this method is + // called after the user has selected another tab. + // Pass the anchor that initiated this request allows + // loadError to manipulate the tab content panel via $(a.hash) + o.ajaxOptions.error( xhr, s, index, a ); + } + catch ( e ) {} + } + } ) ); + + // last, so that load event is fired before show... + self.element.dequeue( "tabs" ); + + return this; + }, + + abort: function() { + // stop possibly running animations + this.element.queue( [] ); + this.panels.stop( false, true ); + + // "tabs" queue must not contain more than two elements, + // which are the callbacks for the latest clicked tab... + this.element.queue( "tabs", this.element.queue( "tabs" ).splice( -2, 2 ) ); + + // terminate pending requests from other tabs + if ( this.xhr ) { + this.xhr.abort(); + delete this.xhr; + } + + // take care of tab labels + this._cleanup(); + return this; + }, + + url: function( index, url ) { + this.anchors.eq( index ).removeData( "cache.tabs" ).data( "load.tabs", url ); + return this; + }, + + length: function() { + return this.anchors.length; + } +}); + +$.extend( $.ui.tabs, { + version: "1.8.11" +}); + +/* + * Tabs Extensions + */ + +/* + * Rotate + */ +$.extend( $.ui.tabs.prototype, { + rotation: null, + rotate: function( ms, continuing ) { + var self = this, + o = this.options; + + var rotate = self._rotate || ( self._rotate = function( e ) { + clearTimeout( self.rotation ); + self.rotation = setTimeout(function() { + var t = o.selected; + self.select( ++t < self.anchors.length ? t : 0 ); + }, ms ); + + if ( e ) { + e.stopPropagation(); + } + }); + + var stop = self._unrotate || ( self._unrotate = !continuing + ? function(e) { + if (e.clientX) { // in case of a true click + self.rotate(null); + } + } + : function( e ) { + t = o.selected; + rotate(); + }); + + // start rotation + if ( ms ) { + this.element.bind( "tabsshow", rotate ); + this.anchors.bind( o.event + ".tabs", stop ); + rotate(); + // stop rotation + } else { + clearTimeout( self.rotation ); + this.element.unbind( "tabsshow", rotate ); + this.anchors.unbind( o.event + ".tabs", stop ); + delete this._rotate; + delete this._unrotate; + } + + return this; + } +}); + +})( jQuery ); diff --git a/recipebook/public/javascripts/jquery-ui.min.js b/recipebook/public/javascripts/jquery-ui.min.js new file mode 100644 index 00000000000..e3badaafd12 --- /dev/null +++ b/recipebook/public/javascripts/jquery-ui.min.js @@ -0,0 +1,406 @@ +/*! + * jQuery UI 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI + */ +(function(b,d){function e(g){return!b(g).parents().andSelf().filter(function(){return b.curCSS(this,"visibility")==="hidden"||b.expr.filters.hidden(this)}).length}b.ui=b.ui||{};if(!b.ui.version){b.extend(b.ui,{version:"1.8.11",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106, +NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});b.fn.extend({_focus:b.fn.focus,focus:function(g,f){return typeof g==="number"?this.each(function(){var a=this;setTimeout(function(){b(a).focus();f&&f.call(a)},g)}):this._focus.apply(this,arguments)},scrollParent:function(){var g;g=b.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(b.curCSS(this, +"position",1))&&/(auto|scroll)/.test(b.curCSS(this,"overflow",1)+b.curCSS(this,"overflow-y",1)+b.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(b.curCSS(this,"overflow",1)+b.curCSS(this,"overflow-y",1)+b.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!g.length?b(document):g},zIndex:function(g){if(g!==d)return this.css("zIndex",g);if(this.length){g=b(this[0]);for(var f;g.length&&g[0]!==document;){f=g.css("position"); +if(f==="absolute"||f==="relative"||f==="fixed"){f=parseInt(g.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}g=g.parent()}}return 0},disableSelection:function(){return this.bind((b.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(g){g.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});b.each(["Width","Height"],function(g,f){function a(j,n,p,l){b.each(c,function(){n-=parseFloat(b.curCSS(j,"padding"+this,true))||0;if(p)n-=parseFloat(b.curCSS(j, +"border"+this+"Width",true))||0;if(l)n-=parseFloat(b.curCSS(j,"margin"+this,true))||0});return n}var c=f==="Width"?["Left","Right"]:["Top","Bottom"],h=f.toLowerCase(),i={innerWidth:b.fn.innerWidth,innerHeight:b.fn.innerHeight,outerWidth:b.fn.outerWidth,outerHeight:b.fn.outerHeight};b.fn["inner"+f]=function(j){if(j===d)return i["inner"+f].call(this);return this.each(function(){b(this).css(h,a(this,j)+"px")})};b.fn["outer"+f]=function(j,n){if(typeof j!=="number")return i["outer"+f].call(this,j);return this.each(function(){b(this).css(h, +a(this,j,true,n)+"px")})}});b.extend(b.expr[":"],{data:function(g,f,a){return!!b.data(g,a[3])},focusable:function(g){var f=g.nodeName.toLowerCase(),a=b.attr(g,"tabindex");if("area"===f){f=g.parentNode;a=f.name;if(!g.href||!a||f.nodeName.toLowerCase()!=="map")return false;g=b("img[usemap=#"+a+"]")[0];return!!g&&e(g)}return(/input|select|textarea|button|object/.test(f)?!g.disabled:"a"==f?g.href||!isNaN(a):!isNaN(a))&&e(g)},tabbable:function(g){var f=b.attr(g,"tabindex");return(isNaN(f)||f>=0)&&b(g).is(":focusable")}}); +b(function(){var g=document.body,f=g.appendChild(f=document.createElement("div"));b.extend(f.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});b.support.minHeight=f.offsetHeight===100;b.support.selectstart="onselectstart"in f;g.removeChild(f).style.display="none"});b.extend(b.ui,{plugin:{add:function(g,f,a){g=b.ui[g].prototype;for(var c in a){g.plugins[c]=g.plugins[c]||[];g.plugins[c].push([f,a[c]])}},call:function(g,f,a){if((f=g.plugins[f])&&g.element[0].parentNode)for(var c=0;c0)return true;g[f]=1;a=g[f]>0;g[f]=0;return a},isOverAxis:function(g,f,a){return g>f&&g=9)&&!d.button)return this._mouseUp(d);if(this._mouseStarted){this._mouseDrag(d);return d.preventDefault()}if(this._mouseDistanceMet(d)&&this._mouseDelayMet(d))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,d)!==false)?this._mouseDrag(d):this._mouseUp(d);return!this._mouseStarted},_mouseUp:function(d){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate); +if(this._mouseStarted){this._mouseStarted=false;d.target==this._mouseDownEvent.target&&b.data(d.target,this.widgetName+".preventClickEvent",true);this._mouseStop(d)}return false},_mouseDistanceMet:function(d){return Math.max(Math.abs(this._mouseDownEvent.pageX-d.pageX),Math.abs(this._mouseDownEvent.pageY-d.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); +(function(b){b.widget("ui.draggable",b.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper== +"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(d){var e= +this.options;if(this.helper||e.disabled||b(d.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(d);if(!this.handle)return false;return true},_mouseStart:function(d){var e=this.options;this.helper=this._createHelper(d);this._cacheHelperProportions();if(b.ui.ddmanager)b.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top- +this.margins.top,left:this.offset.left-this.margins.left};b.extend(this.offset,{click:{left:d.pageX-this.offset.left,top:d.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this.position=this._generatePosition(d);this.originalPageX=d.pageX;this.originalPageY=d.pageY;e.cursorAt&&this._adjustOffsetFromHelper(e.cursorAt);e.containment&&this._setContainment();if(this._trigger("start",d)===false){this._clear();return false}this._cacheHelperProportions(); +b.ui.ddmanager&&!e.dropBehaviour&&b.ui.ddmanager.prepareOffsets(this,d);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(d,true);return true},_mouseDrag:function(d,e){this.position=this._generatePosition(d);this.positionAbs=this._convertPositionTo("absolute");if(!e){e=this._uiHash();if(this._trigger("drag",d,e)===false){this._mouseUp({});return false}this.position=e.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis|| +this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";b.ui.ddmanager&&b.ui.ddmanager.drag(this,d);return false},_mouseStop:function(d){var e=false;if(b.ui.ddmanager&&!this.options.dropBehaviour)e=b.ui.ddmanager.drop(this,d);if(this.dropped){e=this.dropped;this.dropped=false}if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return false;if(this.options.revert=="invalid"&&!e||this.options.revert=="valid"&&e||this.options.revert===true||b.isFunction(this.options.revert)&& +this.options.revert.call(this.element,e)){var g=this;b(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){g._trigger("stop",d)!==false&&g._clear()})}else this._trigger("stop",d)!==false&&this._clear();return false},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(d){var e=!this.options.handle||!b(this.options.handle,this.element).length?true:false;b(this.options.handle,this.element).find("*").andSelf().each(function(){if(this== +d.target)e=true});return e},_createHelper:function(d){var e=this.options;d=b.isFunction(e.helper)?b(e.helper.apply(this.element[0],[d])):e.helper=="clone"?this.element.clone():this.element;d.parents("body").length||d.appendTo(e.appendTo=="parent"?this.element[0].parentNode:e.appendTo);d[0]!=this.element[0]&&!/(fixed|absolute)/.test(d.css("position"))&&d.css("position","absolute");return d},_adjustOffsetFromHelper:function(d){if(typeof d=="string")d=d.split(" ");if(b.isArray(d))d={left:+d[0],top:+d[1]|| +0};if("left"in d)this.offset.click.left=d.left+this.margins.left;if("right"in d)this.offset.click.left=this.helperProportions.width-d.right+this.margins.left;if("top"in d)this.offset.click.top=d.top+this.margins.top;if("bottom"in d)this.offset.click.top=this.helperProportions.height-d.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var d=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&b.ui.contains(this.scrollParent[0], +this.offsetParent[0])){d.left+=this.scrollParent.scrollLeft();d.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&b.browser.msie)d={top:0,left:0};return{top:d.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:d.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var d=this.element.position();return{top:d.top- +(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:d.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(), +height:this.helper.outerHeight()}},_setContainment:function(){var d=this.options;if(d.containment=="parent")d.containment=this.helper[0].parentNode;if(d.containment=="document"||d.containment=="window")this.containment=[(d.containment=="document"?0:b(window).scrollLeft())-this.offset.relative.left-this.offset.parent.left,(d.containment=="document"?0:b(window).scrollTop())-this.offset.relative.top-this.offset.parent.top,(d.containment=="document"?0:b(window).scrollLeft())+b(d.containment=="document"? +document:window).width()-this.helperProportions.width-this.margins.left,(d.containment=="document"?0:b(window).scrollTop())+(b(d.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(d.containment)&&d.containment.constructor!=Array){var e=b(d.containment)[0];if(e){d=b(d.containment).offset();var g=b(e).css("overflow")!="hidden";this.containment=[d.left+(parseInt(b(e).css("borderLeftWidth"), +10)||0)+(parseInt(b(e).css("paddingLeft"),10)||0),d.top+(parseInt(b(e).css("borderTopWidth"),10)||0)+(parseInt(b(e).css("paddingTop"),10)||0),d.left+(g?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(b(e).css("borderLeftWidth"),10)||0)-(parseInt(b(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,d.top+(g?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(b(e).css("borderTopWidth"),10)||0)-(parseInt(b(e).css("paddingBottom"), +10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom]}}else if(d.containment.constructor==Array)this.containment=d.containment},_convertPositionTo:function(d,e){if(!e)e=this.position;d=d=="absolute"?1:-1;var g=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&b.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(g[0].tagName);return{top:e.top+this.offset.relative.top*d+this.offset.parent.top*d-(b.browser.safari&& +b.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:g.scrollTop())*d),left:e.left+this.offset.relative.left*d+this.offset.parent.left*d-(b.browser.safari&&b.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:g.scrollLeft())*d)}},_generatePosition:function(d){var e=this.options,g=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&b.ui.contains(this.scrollParent[0], +this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(g[0].tagName),a=d.pageX,c=d.pageY;if(this.originalPosition){if(this.containment){if(d.pageX-this.offset.click.leftthis.containment[2])a=this.containment[2]+this.offset.click.left;if(d.pageY-this.offset.click.top>this.containment[3])c= +this.containment[3]+this.offset.click.top}if(e.grid){c=this.originalPageY+Math.round((c-this.originalPageY)/e.grid[1])*e.grid[1];c=this.containment?!(c-this.offset.click.topthis.containment[3])?c:!(c-this.offset.click.topthis.containment[2])? +a:!(a-this.offset.click.left').css({width:this.offsetWidth+ +"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(b(this).offset()).appendTo("body")})},stop:function(){b("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)})}});b.ui.plugin.add("draggable","opacity",{start:function(d,e){d=b(e.helper);e=b(this).data("draggable").options;if(d.css("opacity"))e._opacity=d.css("opacity");d.css("opacity",e.opacity)},stop:function(d,e){d=b(this).data("draggable").options;d._opacity&&b(e.helper).css("opacity", +d._opacity)}});b.ui.plugin.add("draggable","scroll",{start:function(){var d=b(this).data("draggable");if(d.scrollParent[0]!=document&&d.scrollParent[0].tagName!="HTML")d.overflowOffset=d.scrollParent.offset()},drag:function(d){var e=b(this).data("draggable"),g=e.options,f=false;if(e.scrollParent[0]!=document&&e.scrollParent[0].tagName!="HTML"){if(!g.axis||g.axis!="x")if(e.overflowOffset.top+e.scrollParent[0].offsetHeight-d.pageY=0;n--){var p=g.snapElements[n].left,l=p+g.snapElements[n].width,k=g.snapElements[n].top,m=k+g.snapElements[n].height;if(p-a=n&&c<=p||h>=n&&h<=p||cp)&&(f>= +i&&f<=j||a>=i&&a<=j||fj);default:return false}};b.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(d,e){var g=b.ui.ddmanager.droppables[d.options.scope]||[],f=e?e.type:null,a=(d.currentItem||d.element).find(":data(droppable)").andSelf(),c=0;a:for(;c').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(), +top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle= +this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=f.handles||(!b(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne", +nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all")this.handles="n,e,s,w,se,sw,ne,nw";var a=this.handles.split(",");this.handles={};for(var c=0;c');/sw|se|ne|nw/.test(h)&&i.css({zIndex:++f.zIndex});"se"==h&&i.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[h]=".ui-resizable-"+h;this.element.append(i)}}this._renderAxis=function(j){j=j||this.element;for(var n in this.handles){if(this.handles[n].constructor== +String)this.handles[n]=b(this.handles[n],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var p=b(this.handles[n],this.element),l=0;l=/sw|ne|nw|se|n|s/.test(n)?p.outerHeight():p.outerWidth();p=["padding",/ne|nw|n/.test(n)?"Top":/se|sw|s/.test(n)?"Bottom":/^e$/.test(n)?"Right":"Left"].join("");j.css(p,l);this._proportionallyResize()}b(this.handles[n])}};this._renderAxis(this.element);this._handles=b(".ui-resizable-handle",this.element).disableSelection(); +this._handles.mouseover(function(){if(!g.resizing){if(this.className)var j=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);g.axis=j&&j[1]?j[1]:"se"}});if(f.autoHide){this._handles.hide();b(this.element).addClass("ui-resizable-autohide").hover(function(){b(this).removeClass("ui-resizable-autohide");g._handles.show()},function(){if(!g.resizing){b(this).addClass("ui-resizable-autohide");g._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var g=function(a){b(a).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()}; +if(this.elementIsWrapper){g(this.element);var f=this.element;f.after(this.originalElement.css({position:f.css("position"),width:f.outerWidth(),height:f.outerHeight(),top:f.css("top"),left:f.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);g(this.originalElement);return this},_mouseCapture:function(g){var f=false;for(var a in this.handles)if(b(this.handles[a])[0]==g.target)f=true;return!this.options.disabled&&f},_mouseStart:function(g){var f=this.options,a=this.element.position(), +c=this.element;this.resizing=true;this.documentScroll={top:b(document).scrollTop(),left:b(document).scrollLeft()};if(c.is(".ui-draggable")||/absolute/.test(c.css("position")))c.css({position:"absolute",top:a.top,left:a.left});b.browser.opera&&/relative/.test(c.css("position"))&&c.css({position:"relative",top:"auto",left:"auto"});this._renderProxy();a=d(this.helper.css("left"));var h=d(this.helper.css("top"));if(f.containment){a+=b(f.containment).scrollLeft()||0;h+=b(f.containment).scrollTop()||0}this.offset= +this.helper.offset();this.position={left:a,top:h};this.size=this._helper?{width:c.outerWidth(),height:c.outerHeight()}:{width:c.width(),height:c.height()};this.originalSize=this._helper?{width:c.outerWidth(),height:c.outerHeight()}:{width:c.width(),height:c.height()};this.originalPosition={left:a,top:h};this.sizeDiff={width:c.outerWidth()-c.width(),height:c.outerHeight()-c.height()};this.originalMousePosition={left:g.pageX,top:g.pageY};this.aspectRatio=typeof f.aspectRatio=="number"?f.aspectRatio: +this.originalSize.width/this.originalSize.height||1;f=b(".ui-resizable-"+this.axis).css("cursor");b("body").css("cursor",f=="auto"?this.axis+"-resize":f);c.addClass("ui-resizable-resizing");this._propagate("start",g);return true},_mouseDrag:function(g){var f=this.helper,a=this.originalMousePosition,c=this._change[this.axis];if(!c)return false;a=c.apply(this,[g,g.pageX-a.left||0,g.pageY-a.top||0]);if(this._aspectRatio||g.shiftKey)a=this._updateRatio(a,g);a=this._respectSize(a,g);this._propagate("resize", +g);f.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(a);this._trigger("resize",g,this.ui());return false},_mouseStop:function(g){this.resizing=false;var f=this.options,a=this;if(this._helper){var c=this._proportionallyResizeElements,h=c.length&&/textarea/i.test(c[0].nodeName);c=h&&b.ui.hasScroll(c[0],"left")?0:a.sizeDiff.height; +h=h?0:a.sizeDiff.width;h={width:a.helper.width()-h,height:a.helper.height()-c};c=parseInt(a.element.css("left"),10)+(a.position.left-a.originalPosition.left)||null;var i=parseInt(a.element.css("top"),10)+(a.position.top-a.originalPosition.top)||null;f.animate||this.element.css(b.extend(h,{top:i,left:c}));a.helper.height(a.size.height);a.helper.width(a.size.width);this._helper&&!f.animate&&this._proportionallyResize()}b("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing"); +this._propagate("stop",g);this._helper&&this.helper.remove();return false},_updateCache:function(g){this.offset=this.helper.offset();if(e(g.left))this.position.left=g.left;if(e(g.top))this.position.top=g.top;if(e(g.height))this.size.height=g.height;if(e(g.width))this.size.width=g.width},_updateRatio:function(g){var f=this.position,a=this.size,c=this.axis;if(g.height)g.width=a.height*this.aspectRatio;else if(g.width)g.height=a.width/this.aspectRatio;if(c=="sw"){g.left=f.left+(a.width-g.width);g.top= +null}if(c=="nw"){g.top=f.top+(a.height-g.height);g.left=f.left+(a.width-g.width)}return g},_respectSize:function(g){var f=this.options,a=this.axis,c=e(g.width)&&f.maxWidth&&f.maxWidthg.width,j=e(g.height)&&f.minHeight&&f.minHeight>g.height;if(i)g.width=f.minWidth;if(j)g.height=f.minHeight;if(c)g.width=f.maxWidth;if(h)g.height=f.maxHeight;var n=this.originalPosition.left+this.originalSize.width,p=this.position.top+ +this.size.height,l=/sw|nw|w/.test(a);a=/nw|ne|n/.test(a);if(i&&l)g.left=n-f.minWidth;if(c&&l)g.left=n-f.maxWidth;if(j&&a)g.top=p-f.minHeight;if(h&&a)g.top=p-f.maxHeight;if((f=!g.width&&!g.height)&&!g.left&&g.top)g.top=null;else if(f&&!g.top&&g.left)g.left=null;return g},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var g=this.helper||this.element,f=0;f');var f=b.browser.msie&&b.browser.version<7,a=f?1:0;f=f?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+f,height:this.element.outerHeight()+f,position:"absolute",left:this.elementOffset.left-a+"px",top:this.elementOffset.top-a+"px",zIndex:++g.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(g, +f){return{width:this.originalSize.width+f}},w:function(g,f){return{left:this.originalPosition.left+f,width:this.originalSize.width-f}},n:function(g,f,a){return{top:this.originalPosition.top+a,height:this.originalSize.height-a}},s:function(g,f,a){return{height:this.originalSize.height+a}},se:function(g,f,a){return b.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[g,f,a]))},sw:function(g,f,a){return b.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[g,f, +a]))},ne:function(g,f,a){return b.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[g,f,a]))},nw:function(g,f,a){return b.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[g,f,a]))}},_propagate:function(g,f){b.ui.plugin.call(this,g,[f,this.ui()]);g!="resize"&&this._trigger(g,f,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize, +originalPosition:this.originalPosition}}});b.extend(b.ui.resizable,{version:"1.8.11"});b.ui.plugin.add("resizable","alsoResize",{start:function(){var g=b(this).data("resizable").options,f=function(a){b(a).each(function(){var c=b(this);c.data("resizable-alsoresize",{width:parseInt(c.width(),10),height:parseInt(c.height(),10),left:parseInt(c.css("left"),10),top:parseInt(c.css("top"),10),position:c.css("position")})})};if(typeof g.alsoResize=="object"&&!g.alsoResize.parentNode)if(g.alsoResize.length){g.alsoResize= +g.alsoResize[0];f(g.alsoResize)}else b.each(g.alsoResize,function(a){f(a)});else f(g.alsoResize)},resize:function(g,f){var a=b(this).data("resizable");g=a.options;var c=a.originalSize,h=a.originalPosition,i={height:a.size.height-c.height||0,width:a.size.width-c.width||0,top:a.position.top-h.top||0,left:a.position.left-h.left||0},j=function(n,p){b(n).each(function(){var l=b(this),k=b(this).data("resizable-alsoresize"),m={},o=p&&p.length?p:l.parents(f.originalElement[0]).length?["width","height"]:["width", +"height","top","left"];b.each(o,function(q,s){if((q=(k[s]||0)+(i[s]||0))&&q>=0)m[s]=q||null});if(b.browser.opera&&/relative/.test(l.css("position"))){a._revertToRelativePosition=true;l.css({position:"absolute",top:"auto",left:"auto"})}l.css(m)})};typeof g.alsoResize=="object"&&!g.alsoResize.nodeType?b.each(g.alsoResize,function(n,p){j(n,p)}):j(g.alsoResize)},stop:function(){var g=b(this).data("resizable"),f=g.options,a=function(c){b(c).each(function(){var h=b(this);h.css({position:h.data("resizable-alsoresize").position})})}; +if(g._revertToRelativePosition){g._revertToRelativePosition=false;typeof f.alsoResize=="object"&&!f.alsoResize.nodeType?b.each(f.alsoResize,function(c){a(c)}):a(f.alsoResize)}b(this).removeData("resizable-alsoresize")}});b.ui.plugin.add("resizable","animate",{stop:function(g){var f=b(this).data("resizable"),a=f.options,c=f._proportionallyResizeElements,h=c.length&&/textarea/i.test(c[0].nodeName),i=h&&b.ui.hasScroll(c[0],"left")?0:f.sizeDiff.height;h={width:f.size.width-(h?0:f.sizeDiff.width),height:f.size.height- +i};i=parseInt(f.element.css("left"),10)+(f.position.left-f.originalPosition.left)||null;var j=parseInt(f.element.css("top"),10)+(f.position.top-f.originalPosition.top)||null;f.element.animate(b.extend(h,j&&i?{top:j,left:i}:{}),{duration:a.animateDuration,easing:a.animateEasing,step:function(){var n={width:parseInt(f.element.css("width"),10),height:parseInt(f.element.css("height"),10),top:parseInt(f.element.css("top"),10),left:parseInt(f.element.css("left"),10)};c&&c.length&&b(c[0]).css({width:n.width, +height:n.height});f._updateCache(n);f._propagate("resize",g)}})}});b.ui.plugin.add("resizable","containment",{start:function(){var g=b(this).data("resizable"),f=g.element,a=g.options.containment;if(f=a instanceof b?a.get(0):/parent/.test(a)?f.parent().get(0):a){g.containerElement=b(f);if(/document/.test(a)||a==document){g.containerOffset={left:0,top:0};g.containerPosition={left:0,top:0};g.parentData={element:b(document),left:0,top:0,width:b(document).width(),height:b(document).height()||document.body.parentNode.scrollHeight}}else{var c= +b(f),h=[];b(["Top","Right","Left","Bottom"]).each(function(n,p){h[n]=d(c.css("padding"+p))});g.containerOffset=c.offset();g.containerPosition=c.position();g.containerSize={height:c.innerHeight()-h[3],width:c.innerWidth()-h[1]};a=g.containerOffset;var i=g.containerSize.height,j=g.containerSize.width;j=b.ui.hasScroll(f,"left")?f.scrollWidth:j;i=b.ui.hasScroll(f)?f.scrollHeight:i;g.parentData={element:f,left:a.left,top:a.top,width:j,height:i}}}},resize:function(g){var f=b(this).data("resizable"),a=f.options, +c=f.containerOffset,h=f.position;g=f._aspectRatio||g.shiftKey;var i={top:0,left:0},j=f.containerElement;if(j[0]!=document&&/static/.test(j.css("position")))i=c;if(h.left<(f._helper?c.left:0)){f.size.width+=f._helper?f.position.left-c.left:f.position.left-i.left;if(g)f.size.height=f.size.width/a.aspectRatio;f.position.left=a.helper?c.left:0}if(h.top<(f._helper?c.top:0)){f.size.height+=f._helper?f.position.top-c.top:f.position.top;if(g)f.size.width=f.size.height*a.aspectRatio;f.position.top=f._helper? +c.top:0}f.offset.left=f.parentData.left+f.position.left;f.offset.top=f.parentData.top+f.position.top;a=Math.abs((f._helper?f.offset.left-i.left:f.offset.left-i.left)+f.sizeDiff.width);c=Math.abs((f._helper?f.offset.top-i.top:f.offset.top-c.top)+f.sizeDiff.height);h=f.containerElement.get(0)==f.element.parent().get(0);i=/relative|absolute/.test(f.containerElement.css("position"));if(h&&i)a-=f.parentData.left;if(a+f.size.width>=f.parentData.width){f.size.width=f.parentData.width-a;if(g)f.size.height= +f.size.width/f.aspectRatio}if(c+f.size.height>=f.parentData.height){f.size.height=f.parentData.height-c;if(g)f.size.width=f.size.height*f.aspectRatio}},stop:function(){var g=b(this).data("resizable"),f=g.options,a=g.containerOffset,c=g.containerPosition,h=g.containerElement,i=b(g.helper),j=i.offset(),n=i.outerWidth()-g.sizeDiff.width;i=i.outerHeight()-g.sizeDiff.height;g._helper&&!f.animate&&/relative/.test(h.css("position"))&&b(this).css({left:j.left-c.left-a.left,width:n,height:i});g._helper&&!f.animate&& +/static/.test(h.css("position"))&&b(this).css({left:j.left-c.left-a.left,width:n,height:i})}});b.ui.plugin.add("resizable","ghost",{start:function(){var g=b(this).data("resizable"),f=g.options,a=g.size;g.ghost=g.originalElement.clone();g.ghost.css({opacity:0.25,display:"block",position:"relative",height:a.height,width:a.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof f.ghost=="string"?f.ghost:"");g.ghost.appendTo(g.helper)},resize:function(){var g=b(this).data("resizable"); +g.ghost&&g.ghost.css({position:"relative",height:g.size.height,width:g.size.width})},stop:function(){var g=b(this).data("resizable");g.ghost&&g.helper&&g.helper.get(0).removeChild(g.ghost.get(0))}});b.ui.plugin.add("resizable","grid",{resize:function(){var g=b(this).data("resizable"),f=g.options,a=g.size,c=g.originalSize,h=g.originalPosition,i=g.axis;f.grid=typeof f.grid=="number"?[f.grid,f.grid]:f.grid;var j=Math.round((a.width-c.width)/(f.grid[0]||1))*(f.grid[0]||1);f=Math.round((a.height-c.height)/ +(f.grid[1]||1))*(f.grid[1]||1);if(/^(se|s|e)$/.test(i)){g.size.width=c.width+j;g.size.height=c.height+f}else if(/^(ne)$/.test(i)){g.size.width=c.width+j;g.size.height=c.height+f;g.position.top=h.top-f}else{if(/^(sw)$/.test(i)){g.size.width=c.width+j;g.size.height=c.height+f}else{g.size.width=c.width+j;g.size.height=c.height+f;g.position.top=h.top-f}g.position.left=h.left-j}}});var d=function(g){return parseInt(g,10)||0},e=function(g){return!isNaN(parseInt(g,10))}})(jQuery); +(function(b){b.widget("ui.selectable",b.ui.mouse,{options:{appendTo:"body",autoRefresh:true,distance:0,filter:"*",tolerance:"touch"},_create:function(){var d=this;this.element.addClass("ui-selectable");this.dragged=false;var e;this.refresh=function(){e=b(d.options.filter,d.element[0]);e.each(function(){var g=b(this),f=g.offset();b.data(this,"selectable-item",{element:this,$element:g,left:f.left,top:f.top,right:f.left+g.outerWidth(),bottom:f.top+g.outerHeight(),startselected:false,selected:g.hasClass("ui-selected"), +selecting:g.hasClass("ui-selecting"),unselecting:g.hasClass("ui-unselecting")})})};this.refresh();this.selectees=e.addClass("ui-selectee");this._mouseInit();this.helper=b("
      ")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item");this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy();return this},_mouseStart:function(d){var e=this;this.opos=[d.pageX, +d.pageY];if(!this.options.disabled){var g=this.options;this.selectees=b(g.filter,this.element[0]);this._trigger("start",d);b(g.appendTo).append(this.helper);this.helper.css({left:d.clientX,top:d.clientY,width:0,height:0});g.autoRefresh&&this.refresh();this.selectees.filter(".ui-selected").each(function(){var f=b.data(this,"selectable-item");f.startselected=true;if(!d.metaKey){f.$element.removeClass("ui-selected");f.selected=false;f.$element.addClass("ui-unselecting");f.unselecting=true;e._trigger("unselecting", +d,{unselecting:f.element})}});b(d.target).parents().andSelf().each(function(){var f=b.data(this,"selectable-item");if(f){var a=!d.metaKey||!f.$element.hasClass("ui-selected");f.$element.removeClass(a?"ui-unselecting":"ui-selected").addClass(a?"ui-selecting":"ui-unselecting");f.unselecting=!a;f.selecting=a;(f.selected=a)?e._trigger("selecting",d,{selecting:f.element}):e._trigger("unselecting",d,{unselecting:f.element});return false}})}},_mouseDrag:function(d){var e=this;this.dragged=true;if(!this.options.disabled){var g= +this.options,f=this.opos[0],a=this.opos[1],c=d.pageX,h=d.pageY;if(f>c){var i=c;c=f;f=i}if(a>h){i=h;h=a;a=i}this.helper.css({left:f,top:a,width:c-f,height:h-a});this.selectees.each(function(){var j=b.data(this,"selectable-item");if(!(!j||j.element==e.element[0])){var n=false;if(g.tolerance=="touch")n=!(j.left>c||j.righth||j.bottomf&&j.righta&&j.bottom *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1E3},_create:function(){this.containerCache={};this.element.addClass("ui-sortable"); +this.refresh();this.floating=this.items.length?/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var d=this.items.length-1;d>=0;d--)this.items[d].item.removeData("sortable-item");return this},_setOption:function(d,e){if(d==="disabled"){this.options[d]= +e;this.widget()[e?"addClass":"removeClass"]("ui-sortable-disabled")}else b.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(d,e){if(this.reverting)return false;if(this.options.disabled||this.options.type=="static")return false;this._refreshItems(d);var g=null,f=this;b(d.target).parents().each(function(){if(b.data(this,"sortable-item")==f){g=b(this);return false}});if(b.data(d.target,"sortable-item")==f)g=b(d.target);if(!g)return false;if(this.options.handle&&!e){var a=false; +b(this.options.handle,g).find("*").andSelf().each(function(){if(this==d.target)a=true});if(!a)return false}this.currentItem=g;this._removeCurrentsFromItems();return true},_mouseStart:function(d,e,g){e=this.options;var f=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(d);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left- +this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");b.extend(this.offset,{click:{left:d.pageX-this.offset.left,top:d.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(d);this.originalPageX=d.pageX;this.originalPageY=d.pageY;e.cursorAt&&this._adjustOffsetFromHelper(e.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]}; +this.helper[0]!=this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder();e.containment&&this._setContainment();if(e.cursor){if(b("body").css("cursor"))this._storedCursor=b("body").css("cursor");b("body").css("cursor",e.cursor)}if(e.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",e.opacity)}if(e.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",e.zIndex)}if(this.scrollParent[0]!= +document&&this.scrollParent[0].tagName!="HTML")this.overflowOffset=this.scrollParent.offset();this._trigger("start",d,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!g)for(g=this.containers.length-1;g>=0;g--)this.containers[g]._trigger("activate",d,f._uiHash(this));if(b.ui.ddmanager)b.ui.ddmanager.current=this;b.ui.ddmanager&&!e.dropBehaviour&&b.ui.ddmanager.prepareOffsets(this,d);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(d); +return true},_mouseDrag:function(d){this.position=this._generatePosition(d);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs)this.lastPositionAbs=this.positionAbs;if(this.options.scroll){var e=this.options,g=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if(this.overflowOffset.top+this.scrollParent[0].offsetHeight-d.pageY=0;e--){g=this.items[e];var f=g.item[0],a=this._intersectsWithPointer(g);if(a)if(f!=this.currentItem[0]&&this.placeholder[a==1?"next":"prev"]()[0]!=f&&!b.ui.contains(this.placeholder[0],f)&&(this.options.type=="semi-dynamic"?!b.ui.contains(this.element[0], +f):true)){this.direction=a==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(g))this._rearrange(d,g);else break;this._trigger("change",d,this._uiHash());break}}this._contactContainers(d);b.ui.ddmanager&&b.ui.ddmanager.drag(this,d);this._trigger("sort",d,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(d,e){if(d){b.ui.ddmanager&&!this.options.dropBehaviour&&b.ui.ddmanager.drop(this,d);if(this.options.revert){var g=this;e=g.placeholder.offset(); +g.reverting=true;b(this.helper).animate({left:e.left-this.offset.parent.left-g.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:e.top-this.offset.parent.top-g.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){g._clear(d)})}else this._clear(d,e);return false}},cancel:function(){var d=this;if(this.dragging){this._mouseUp({target:null});this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"): +this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--){this.containers[e]._trigger("deactivate",null,d._uiHash(this));if(this.containers[e].containerCache.over){this.containers[e]._trigger("out",null,d._uiHash(this));this.containers[e].containerCache.over=0}}}if(this.placeholder){this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove();b.extend(this,{helper:null, +dragging:false,reverting:false,_noFinalSort:null});this.domPosition.prev?b(this.domPosition.prev).after(this.currentItem):b(this.domPosition.parent).prepend(this.currentItem)}return this},serialize:function(d){var e=this._getItemsAsjQuery(d&&d.connected),g=[];d=d||{};b(e).each(function(){var f=(b(d.item||this).attr(d.attribute||"id")||"").match(d.expression||/(.+)[-=_](.+)/);if(f)g.push((d.key||f[1]+"[]")+"="+(d.key&&d.expression?f[1]:f[2]))});!g.length&&d.key&&g.push(d.key+"=");return g.join("&")}, +toArray:function(d){var e=this._getItemsAsjQuery(d&&d.connected),g=[];d=d||{};e.each(function(){g.push(b(d.item||this).attr(d.attribute||"id")||"")});return g},_intersectsWith:function(d){var e=this.positionAbs.left,g=e+this.helperProportions.width,f=this.positionAbs.top,a=f+this.helperProportions.height,c=d.left,h=c+d.width,i=d.top,j=i+d.height,n=this.offset.click.top,p=this.offset.click.left;n=f+n>i&&f+nc&&e+pd[this.floating?"width":"height"]?n:c0?"down":"up")},_getDragHorizontalDirection:function(){var d=this.positionAbs.left-this.lastPositionAbs.left;return d!=0&&(d>0?"right":"left")},refresh:function(d){this._refreshItems(d);this.refreshPositions();return this},_connectWith:function(){var d=this.options;return d.connectWith.constructor==String?[d.connectWith]:d.connectWith},_getItemsAsjQuery:function(d){var e=[],g=[],f=this._connectWith(); +if(f&&d)for(d=f.length-1;d>=0;d--)for(var a=b(f[d]),c=a.length-1;c>=0;c--){var h=b.data(a[c],"sortable");if(h&&h!=this&&!h.options.disabled)g.push([b.isFunction(h.options.items)?h.options.items.call(h.element):b(h.options.items,h.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),h])}g.push([b.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):b(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), +this]);for(d=g.length-1;d>=0;d--)g[d][0].each(function(){e.push(this)});return b(e)},_removeCurrentsFromItems:function(){for(var d=this.currentItem.find(":data(sortable-item)"),e=0;e=0;a--)for(var c=b(f[a]),h=c.length-1;h>=0;h--){var i=b.data(c[h],"sortable");if(i&&i!=this&&!i.options.disabled){g.push([b.isFunction(i.options.items)?i.options.items.call(i.element[0],d,{item:this.currentItem}):b(i.options.items,i.element),i]);this.containers.push(i)}}for(a=g.length-1;a>=0;a--){d=g[a][1];f=g[a][0];h=0;for(c=f.length;h=0;e--){var g=this.items[e],f=this.options.toleranceElement?b(this.options.toleranceElement,g.item):g.item;if(!d){g.width=f.outerWidth();g.height=f.outerHeight()}f=f.offset();g.left=f.left;g.top=f.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(e=this.containers.length-1;e>=0;e--){f=this.containers[e].element.offset();this.containers[e].containerCache.left= +f.left;this.containers[e].containerCache.top=f.top;this.containers[e].containerCache.width=this.containers[e].element.outerWidth();this.containers[e].containerCache.height=this.containers[e].element.outerHeight()}return this},_createPlaceholder:function(d){var e=d||this,g=e.options;if(!g.placeholder||g.placeholder.constructor==String){var f=g.placeholder;g.placeholder={element:function(){var a=b(document.createElement(e.currentItem[0].nodeName)).addClass(f||e.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0]; +if(!f)a.style.visibility="hidden";return a},update:function(a,c){if(!(f&&!g.forcePlaceholderSize)){c.height()||c.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10));c.width()||c.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10))}}}}e.placeholder=b(g.placeholder.element.call(e.element,e.currentItem));e.currentItem.after(e.placeholder); +g.placeholder.update(e,e.placeholder)},_contactContainers:function(d){for(var e=null,g=null,f=this.containers.length-1;f>=0;f--)if(!b.ui.contains(this.currentItem[0],this.containers[f].element[0]))if(this._intersectsWith(this.containers[f].containerCache)){if(!(e&&b.ui.contains(this.containers[f].element[0],e.element[0]))){e=this.containers[f];g=f}}else if(this.containers[f].containerCache.over){this.containers[f]._trigger("out",d,this._uiHash(this));this.containers[f].containerCache.over=0}if(e)if(this.containers.length=== +1){this.containers[g]._trigger("over",d,this._uiHash(this));this.containers[g].containerCache.over=1}else if(this.currentContainer!=this.containers[g]){e=1E4;f=null;for(var a=this.positionAbs[this.containers[g].floating?"left":"top"],c=this.items.length-1;c>=0;c--)if(b.ui.contains(this.containers[g].element[0],this.items[c].item[0])){var h=this.items[c][this.containers[g].floating?"left":"top"];if(Math.abs(h-a)this.containment[2])a=this.containment[2]+this.offset.click.left;if(d.pageY-this.offset.click.top>this.containment[3])c=this.containment[3]+this.offset.click.top}if(e.grid){c=this.originalPageY+Math.round((c-this.originalPageY)/e.grid[1])*e.grid[1];c=this.containment?!(c-this.offset.click.top< +this.containment[1]||c-this.offset.click.top>this.containment[3])?c:!(c-this.offset.click.topthis.containment[2])?a:!(a-this.offset.click.left=0;f--)if(b.ui.contains(this.containers[f].element[0], +this.currentItem[0])&&!e){g.push(function(a){return function(c){a._trigger("receive",c,this._uiHash(this))}}.call(this,this.containers[f]));g.push(function(a){return function(c){a._trigger("update",c,this._uiHash(this))}}.call(this,this.containers[f]))}}for(f=this.containers.length-1;f>=0;f--){e||g.push(function(a){return function(c){a._trigger("deactivate",c,this._uiHash(this))}}.call(this,this.containers[f]));if(this.containers[f].containerCache.over){g.push(function(a){return function(c){a._trigger("out", +c,this._uiHash(this))}}.call(this,this.containers[f]));this.containers[f].containerCache.over=0}}this._storedCursor&&b("body").css("cursor",this._storedCursor);this._storedOpacity&&this.helper.css("opacity",this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!e){this._trigger("beforeStop",d,this._uiHash());for(f=0;f").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent", +border:"none",margin:0,padding:0});l.wrap(m);m=l.parent();if(l.css("position")=="static"){m.css({position:"relative"});l.css({position:"relative"})}else{b.extend(k,{position:l.css("position"),zIndex:l.css("z-index")});b.each(["top","left","bottom","right"],function(o,q){k[q]=l.css(q);if(isNaN(parseInt(k[q],10)))k[q]="auto"});l.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})}return m.css(k).show()},removeWrapper:function(l){if(l.parent().is(".ui-effects-wrapper"))return l.parent().replaceWith(l); +return l},setTransition:function(l,k,m,o){o=o||{};b.each(k,function(q,s){unit=l.cssUnit(s);if(unit[0]>0)o[s]=unit[0]*m+unit[1]});return o}});b.fn.extend({effect:function(l){var k=h.apply(this,arguments),m={options:k[1],duration:k[2],callback:k[3]};k=m.options.mode;var o=b.effects[l];if(b.fx.off||!o)return k?this[k](m.duration,m.callback):this.each(function(){m.callback&&m.callback.call(this)});return o.call(this,m)},_show:b.fn.show,show:function(l){if(i(l))return this._show.apply(this,arguments); +else{var k=h.apply(this,arguments);k[1].mode="show";return this.effect.apply(this,k)}},_hide:b.fn.hide,hide:function(l){if(i(l))return this._hide.apply(this,arguments);else{var k=h.apply(this,arguments);k[1].mode="hide";return this.effect.apply(this,k)}},__toggle:b.fn.toggle,toggle:function(l){if(i(l)||typeof l==="boolean"||b.isFunction(l))return this.__toggle.apply(this,arguments);else{var k=h.apply(this,arguments);k[1].mode="toggle";return this.effect.apply(this,k)}},cssUnit:function(l){var k=this.css(l), +m=[];b.each(["em","px","%","pt"],function(o,q){if(k.indexOf(q)>0)m=[parseFloat(k),q]});return m}});b.easing.jswing=b.easing.swing;b.extend(b.easing,{def:"easeOutQuad",swing:function(l,k,m,o,q){return b.easing[b.easing.def](l,k,m,o,q)},easeInQuad:function(l,k,m,o,q){return o*(k/=q)*k+m},easeOutQuad:function(l,k,m,o,q){return-o*(k/=q)*(k-2)+m},easeInOutQuad:function(l,k,m,o,q){if((k/=q/2)<1)return o/2*k*k+m;return-o/2*(--k*(k-2)-1)+m},easeInCubic:function(l,k,m,o,q){return o*(k/=q)*k*k+m},easeOutCubic:function(l, +k,m,o,q){return o*((k=k/q-1)*k*k+1)+m},easeInOutCubic:function(l,k,m,o,q){if((k/=q/2)<1)return o/2*k*k*k+m;return o/2*((k-=2)*k*k+2)+m},easeInQuart:function(l,k,m,o,q){return o*(k/=q)*k*k*k+m},easeOutQuart:function(l,k,m,o,q){return-o*((k=k/q-1)*k*k*k-1)+m},easeInOutQuart:function(l,k,m,o,q){if((k/=q/2)<1)return o/2*k*k*k*k+m;return-o/2*((k-=2)*k*k*k-2)+m},easeInQuint:function(l,k,m,o,q){return o*(k/=q)*k*k*k*k+m},easeOutQuint:function(l,k,m,o,q){return o*((k=k/q-1)*k*k*k*k+1)+m},easeInOutQuint:function(l, +k,m,o,q){if((k/=q/2)<1)return o/2*k*k*k*k*k+m;return o/2*((k-=2)*k*k*k*k+2)+m},easeInSine:function(l,k,m,o,q){return-o*Math.cos(k/q*(Math.PI/2))+o+m},easeOutSine:function(l,k,m,o,q){return o*Math.sin(k/q*(Math.PI/2))+m},easeInOutSine:function(l,k,m,o,q){return-o/2*(Math.cos(Math.PI*k/q)-1)+m},easeInExpo:function(l,k,m,o,q){return k==0?m:o*Math.pow(2,10*(k/q-1))+m},easeOutExpo:function(l,k,m,o,q){return k==q?m+o:o*(-Math.pow(2,-10*k/q)+1)+m},easeInOutExpo:function(l,k,m,o,q){if(k==0)return m;if(k== +q)return m+o;if((k/=q/2)<1)return o/2*Math.pow(2,10*(k-1))+m;return o/2*(-Math.pow(2,-10*--k)+2)+m},easeInCirc:function(l,k,m,o,q){return-o*(Math.sqrt(1-(k/=q)*k)-1)+m},easeOutCirc:function(l,k,m,o,q){return o*Math.sqrt(1-(k=k/q-1)*k)+m},easeInOutCirc:function(l,k,m,o,q){if((k/=q/2)<1)return-o/2*(Math.sqrt(1-k*k)-1)+m;return o/2*(Math.sqrt(1-(k-=2)*k)+1)+m},easeInElastic:function(l,k,m,o,q){l=1.70158;var s=0,r=o;if(k==0)return m;if((k/=q)==1)return m+o;s||(s=q*0.3);if(r").css({position:"absolute",visibility:"visible",left:-j*(c/g),top:-i*(h/e)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:c/g,height:h/e,left:a.left+j*(c/g)+(d.options.mode=="show"?(j-Math.floor(g/2))*(c/g):0),top:a.top+i*(h/e)+(d.options.mode=="show"?(i-Math.floor(e/2))*(h/e):0),opacity:d.options.mode=="show"?0:1}).animate({left:a.left+j*(c/g)+(d.options.mode=="show"?0:(j-Math.floor(g/2))*(c/g)),top:a.top+ +i*(h/e)+(d.options.mode=="show"?0:(i-Math.floor(e/2))*(h/e)),opacity:d.options.mode=="show"?1:0},d.duration||500);setTimeout(function(){d.options.mode=="show"?f.css({visibility:"visible"}):f.css({visibility:"visible"}).hide();d.callback&&d.callback.apply(f[0]);f.dequeue();b("div.ui-effects-explode").remove()},d.duration||500)})}})(jQuery); +(function(b){b.effects.fade=function(d){return this.queue(function(){var e=b(this),g=b.effects.setMode(e,d.options.mode||"hide");e.animate({opacity:g},{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){d.callback&&d.callback.apply(this,arguments);e.dequeue()}})})}})(jQuery); +(function(b){b.effects.fold=function(d){return this.queue(function(){var e=b(this),g=["position","top","bottom","left","right"],f=b.effects.setMode(e,d.options.mode||"hide"),a=d.options.size||15,c=!!d.options.horizFirst,h=d.duration?d.duration/2:b.fx.speeds._default/2;b.effects.save(e,g);e.show();var i=b.effects.createWrapper(e).css({overflow:"hidden"}),j=f=="show"!=c,n=j?["width","height"]:["height","width"];j=j?[i.width(),i.height()]:[i.height(),i.width()];var p=/([0-9]+)%/.exec(a);if(p)a=parseInt(p[1], +10)/100*j[f=="hide"?0:1];if(f=="show")i.css(c?{height:0,width:a}:{height:a,width:0});c={};p={};c[n[0]]=f=="show"?j[0]:a;p[n[1]]=f=="show"?j[1]:0;i.animate(c,h,d.options.easing).animate(p,h,d.options.easing,function(){f=="hide"&&e.hide();b.effects.restore(e,g);b.effects.removeWrapper(e);d.callback&&d.callback.apply(e[0],arguments);e.dequeue()})})}})(jQuery); +(function(b){b.effects.highlight=function(d){return this.queue(function(){var e=b(this),g=["backgroundImage","backgroundColor","opacity"],f=b.effects.setMode(e,d.options.mode||"show"),a={backgroundColor:e.css("backgroundColor")};if(f=="hide")a.opacity=0;b.effects.save(e,g);e.show().css({backgroundImage:"none",backgroundColor:d.options.color||"#ffff99"}).animate(a,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){f=="hide"&&e.hide();b.effects.restore(e,g);f=="show"&&!b.support.opacity&& +this.style.removeAttribute("filter");d.callback&&d.callback.apply(this,arguments);e.dequeue()}})})}})(jQuery); +(function(b){b.effects.pulsate=function(d){return this.queue(function(){var e=b(this),g=b.effects.setMode(e,d.options.mode||"show");times=(d.options.times||5)*2-1;duration=d.duration?d.duration/2:b.fx.speeds._default/2;isVisible=e.is(":visible");animateTo=0;if(!isVisible){e.css("opacity",0).show();animateTo=1}if(g=="hide"&&isVisible||g=="show"&&!isVisible)times--;for(g=0;g').appendTo(document.body).addClass(d.options.className).css({top:f.top,left:f.left,height:e.innerHeight(),width:e.innerWidth(),position:"absolute"}).animate(g,d.duration,d.options.easing,function(){a.remove();d.callback&&d.callback.apply(e[0],arguments); +e.dequeue()})})}})(jQuery); +(function(b){b.widget("ui.accordion",{options:{active:0,animated:"slide",autoHeight:true,clearStyle:false,collapsible:false,event:"click",fillSpace:false,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var d=this,e=d.options;d.running=0;d.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix");d.headers= +d.element.find(e.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){e.disabled||b(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){e.disabled||b(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){e.disabled||b(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){e.disabled||b(this).removeClass("ui-state-focus")});d.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom"); +if(e.navigation){var g=d.element.find("a").filter(e.navigationFilter).eq(0);if(g.length){var f=g.closest(".ui-accordion-header");d.active=f.length?f:g.closest(".ui-accordion-content").prev()}}d.active=d._findActive(d.active||e.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top");d.active.next().addClass("ui-accordion-content-active");d._createIcons();d.resize();d.element.attr("role","tablist");d.headers.attr("role","tab").bind("keydown.accordion", +function(a){return d._keydown(a)}).next().attr("role","tabpanel");d.headers.not(d.active||"").attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).next().hide();d.active.length?d.active.attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}):d.headers.eq(0).attr("tabIndex",0);b.browser.safari||d.headers.find("a").attr("tabIndex",-1);e.event&&d.headers.bind(e.event.split(" ").join(".accordion ")+".accordion",function(a){d._clickHandler.call(d,a,this);a.preventDefault()})},_createIcons:function(){var d= +this.options;if(d.icons){b("").addClass("ui-icon "+d.icons.header).prependTo(this.headers);this.active.children(".ui-icon").toggleClass(d.icons.header).toggleClass(d.icons.headerSelected);this.element.addClass("ui-accordion-icons")}},_destroyIcons:function(){this.headers.children(".ui-icon").remove();this.element.removeClass("ui-accordion-icons")},destroy:function(){var d=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("tabIndex"); +this.headers.find("a").removeAttr("tabIndex");this._destroyIcons();var e=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");if(d.autoHeight||d.fillHeight)e.css("height","");return b.Widget.prototype.destroy.call(this)},_setOption:function(d,e){b.Widget.prototype._setOption.apply(this,arguments);d=="active"&&this.activate(e);if(d=="icons"){this._destroyIcons(); +e&&this._createIcons()}if(d=="disabled")this.headers.add(this.headers.next())[e?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(d){if(!(this.options.disabled||d.altKey||d.ctrlKey)){var e=b.ui.keyCode,g=this.headers.length,f=this.headers.index(d.target),a=false;switch(d.keyCode){case e.RIGHT:case e.DOWN:a=this.headers[(f+1)%g];break;case e.LEFT:case e.UP:a=this.headers[(f-1+g)%g];break;case e.SPACE:case e.ENTER:this._clickHandler({target:d.target},d.target); +d.preventDefault()}if(a){b(d.target).attr("tabIndex",-1);b(a).attr("tabIndex",0);a.focus();return false}return true}},resize:function(){var d=this.options,e;if(d.fillSpace){if(b.browser.msie){var g=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}e=this.element.parent().height();b.browser.msie&&this.element.parent().css("overflow",g);this.headers.each(function(){e-=b(this).outerHeight(true)});this.headers.next().each(function(){b(this).height(Math.max(0,e-b(this).innerHeight()+ +b(this).height()))}).css("overflow","auto")}else if(d.autoHeight){e=0;this.headers.next().each(function(){e=Math.max(e,b(this).height("").height())}).height(e)}return this},activate:function(d){this.options.active=d;d=this._findActive(d)[0];this._clickHandler({target:d},d);return this},_findActive:function(d){return d?typeof d==="number"?this.headers.filter(":eq("+d+")"):this.headers.not(this.headers.not(d)):d===false?b([]):this.headers.filter(":eq(0)")},_clickHandler:function(d,e){var g=this.options; +if(!g.disabled)if(d.target){d=b(d.currentTarget||e);e=d[0]===this.active[0];g.active=g.collapsible&&e?false:this.headers.index(d);if(!(this.running||!g.collapsible&&e)){var f=this.active;i=d.next();c=this.active.next();h={options:g,newHeader:e&&g.collapsible?b([]):d,oldHeader:this.active,newContent:e&&g.collapsible?b([]):i,oldContent:c};var a=this.headers.index(this.active[0])>this.headers.index(d[0]);this.active=e?b([]):d;this._toggle(i,c,h,e,a);f.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(g.icons.headerSelected).addClass(g.icons.header); +if(!e){d.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(g.icons.header).addClass(g.icons.headerSelected);d.next().addClass("ui-accordion-content-active")}}}else if(g.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(g.icons.headerSelected).addClass(g.icons.header);this.active.next().addClass("ui-accordion-content-active");var c=this.active.next(), +h={options:g,newHeader:b([]),oldHeader:g.active,newContent:b([]),oldContent:c},i=this.active=b([]);this._toggle(i,c,h)}},_toggle:function(d,e,g,f,a){var c=this,h=c.options;c.toShow=d;c.toHide=e;c.data=g;var i=function(){if(c)return c._completed.apply(c,arguments)};c._trigger("changestart",null,c.data);c.running=e.size()===0?d.size():e.size();if(h.animated){g={};g=h.collapsible&&f?{toShow:b([]),toHide:e,complete:i,down:a,autoHeight:h.autoHeight||h.fillSpace}:{toShow:d,toHide:e,complete:i,down:a,autoHeight:h.autoHeight|| +h.fillSpace};if(!h.proxied)h.proxied=h.animated;if(!h.proxiedDuration)h.proxiedDuration=h.duration;h.animated=b.isFunction(h.proxied)?h.proxied(g):h.proxied;h.duration=b.isFunction(h.proxiedDuration)?h.proxiedDuration(g):h.proxiedDuration;f=b.ui.accordion.animations;var j=h.duration,n=h.animated;if(n&&!f[n]&&!b.easing[n])n="slide";f[n]||(f[n]=function(p){this.slide(p,{easing:n,duration:j||700})});f[n](g)}else{if(h.collapsible&&f)d.toggle();else{e.hide();d.show()}i(true)}e.prev().attr({"aria-expanded":"false", +"aria-selected":"false",tabIndex:-1}).blur();d.prev().attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}).focus()},_completed:function(d){this.running=d?0:--this.running;if(!this.running){this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""});this.toHide.removeClass("ui-accordion-content-active");if(this.toHide.length)this.toHide.parent()[0].className=this.toHide.parent()[0].className;this._trigger("change",null,this.data)}}});b.extend(b.ui.accordion,{version:"1.8.11", +animations:{slide:function(d,e){d=b.extend({easing:"swing",duration:300},d,e);if(d.toHide.size())if(d.toShow.size()){var g=d.toShow.css("overflow"),f=0,a={},c={},h;e=d.toShow;h=e[0].style.width;e.width(parseInt(e.parent().width(),10)-parseInt(e.css("paddingLeft"),10)-parseInt(e.css("paddingRight"),10)-(parseInt(e.css("borderLeftWidth"),10)||0)-(parseInt(e.css("borderRightWidth"),10)||0));b.each(["height","paddingTop","paddingBottom"],function(i,j){c[j]="hide";i=(""+b.css(d.toShow[0],j)).match(/^([\d+-.]+)(.*)$/); +a[j]={value:i[1],unit:i[2]||"px"}});d.toShow.css({height:0,overflow:"hidden"}).show();d.toHide.filter(":hidden").each(d.complete).end().filter(":visible").animate(c,{step:function(i,j){if(j.prop=="height")f=j.end-j.start===0?0:(j.now-j.start)/(j.end-j.start);d.toShow[0].style[j.prop]=f*a[j.prop].value+a[j.prop].unit},duration:d.duration,easing:d.easing,complete:function(){d.autoHeight||d.toShow.css("height","");d.toShow.css({width:h,overflow:g});d.complete()}})}else d.toHide.animate({height:"hide", +paddingTop:"hide",paddingBottom:"hide"},d);else d.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},d)},bounceslide:function(d){this.slide(d,{easing:d.down?"easeOutBounce":"swing",duration:d.down?1E3:200})}}})})(jQuery); +(function(b){var d=0;b.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:false,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var e=this,g=this.element[0].ownerDocument,f;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(a){if(!(e.options.disabled||e.element.attr("readonly"))){f= +false;var c=b.ui.keyCode;switch(a.keyCode){case c.PAGE_UP:e._move("previousPage",a);break;case c.PAGE_DOWN:e._move("nextPage",a);break;case c.UP:e._move("previous",a);a.preventDefault();break;case c.DOWN:e._move("next",a);a.preventDefault();break;case c.ENTER:case c.NUMPAD_ENTER:if(e.menu.active){f=true;a.preventDefault()}case c.TAB:if(!e.menu.active)return;e.menu.select(a);break;case c.ESCAPE:e.element.val(e.term);e.close(a);break;default:clearTimeout(e.searching);e.searching=setTimeout(function(){if(e.term!= +e.element.val()){e.selectedItem=null;e.search(null,a)}},e.options.delay);break}}}).bind("keypress.autocomplete",function(a){if(f){f=false;a.preventDefault()}}).bind("focus.autocomplete",function(){if(!e.options.disabled){e.selectedItem=null;e.previous=e.element.val()}}).bind("blur.autocomplete",function(a){if(!e.options.disabled){clearTimeout(e.searching);e.closing=setTimeout(function(){e.close(a);e._change(a)},150)}});this._initSource();this.response=function(){return e._response.apply(e,arguments)}; +this.menu=b("
        ").addClass("ui-autocomplete").appendTo(b(this.options.appendTo||"body",g)[0]).mousedown(function(a){var c=e.menu.element[0];b(a.target).closest(".ui-menu-item").length||setTimeout(function(){b(document).one("mousedown",function(h){h.target!==e.element[0]&&h.target!==c&&!b.ui.contains(c,h.target)&&e.close()})},1);setTimeout(function(){clearTimeout(e.closing)},13)}).menu({focus:function(a,c){c=c.item.data("item.autocomplete");false!==e._trigger("focus",a,{item:c})&&/^key/.test(a.originalEvent.type)&& +e.element.val(c.value)},selected:function(a,c){var h=c.item.data("item.autocomplete"),i=e.previous;if(e.element[0]!==g.activeElement){e.element.focus();e.previous=i;setTimeout(function(){e.previous=i;e.selectedItem=h},1)}false!==e._trigger("select",a,{item:h})&&e.element.val(h.value);e.term=e.element.val();e.close(a);e.selectedItem=h},blur:function(){e.menu.element.is(":visible")&&e.element.val()!==e.term&&e.element.val(e.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"); +b.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");this.menu.element.remove();b.Widget.prototype.destroy.call(this)},_setOption:function(e,g){b.Widget.prototype._setOption.apply(this,arguments);e==="source"&&this._initSource();if(e==="appendTo")this.menu.element.appendTo(b(g||"body",this.element[0].ownerDocument)[0]);e==="disabled"&& +g&&this.xhr&&this.xhr.abort()},_initSource:function(){var e=this,g,f;if(b.isArray(this.options.source)){g=this.options.source;this.source=function(a,c){c(b.ui.autocomplete.filter(g,a.term))}}else if(typeof this.options.source==="string"){f=this.options.source;this.source=function(a,c){e.xhr&&e.xhr.abort();e.xhr=b.ajax({url:f,data:a,dataType:"json",autocompleteRequest:++d,success:function(h){this.autocompleteRequest===d&&c(h)},error:function(){this.autocompleteRequest===d&&c([])}})}}else this.source= +this.options.source},search:function(e,g){e=e!=null?e:this.element.val();this.term=this.element.val();if(e.length
      • ").data("item.autocomplete",g).append(b("").text(g.label)).appendTo(e)},_move:function(e,g){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(e)||this.menu.last()&&/^next/.test(e)){this.element.val(this.term);this.menu.deactivate()}else this.menu[e](g);else this.search(null,g)},widget:function(){return this.menu.element}});b.extend(b.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, +"\\$&")},filter:function(e,g){var f=new RegExp(b.ui.autocomplete.escapeRegex(g),"i");return b.grep(e,function(a){return f.test(a.label||a.value||a)})}})})(jQuery); +(function(b){b.widget("ui.menu",{_create:function(){var d=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(e){if(b(e.target).closest(".ui-menu-item a").length){e.preventDefault();d.select(e)}});this.refresh()},refresh:function(){var d=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex", +-1).mouseenter(function(e){d.activate(e,b(this).parent())}).mouseleave(function(){d.deactivate()})},activate:function(d,e){this.deactivate();if(this.hasScroll()){var g=e.offset().top-this.element.offset().top,f=this.element.attr("scrollTop"),a=this.element.height();if(g<0)this.element.attr("scrollTop",f+g);else g>=a&&this.element.attr("scrollTop",f+g-a+e.height())}this.active=e.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",d,{item:e})}, +deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");this._trigger("blur");this.active=null}},next:function(d){this.move("next",".ui-menu-item:first",d)},previous:function(d){this.move("prev",".ui-menu-item:last",d)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(d,e,g){if(this.active){d=this.active[d+"All"](".ui-menu-item").eq(0); +d.length?this.activate(g,d):this.activate(g,this.element.children(e))}else this.activate(g,this.element.children(e))},nextPage:function(d){if(this.hasScroll())if(!this.active||this.last())this.activate(d,this.element.children(".ui-menu-item:first"));else{var e=this.active.offset().top,g=this.element.height(),f=this.element.children(".ui-menu-item").filter(function(){var a=b(this).offset().top-e-g+b(this).height();return a<10&&a>-10});f.length||(f=this.element.children(".ui-menu-item:last"));this.activate(d, +f)}else this.activate(d,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(d){if(this.hasScroll())if(!this.active||this.first())this.activate(d,this.element.children(".ui-menu-item:last"));else{var e=this.active.offset().top,g=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var f=b(this).offset().top-e+g-b(this).height();return f<10&&f>-10});result.length||(result=this.element.children(".ui-menu-item:first")); +this.activate(d,result)}else this.activate(d,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()").addClass("ui-button-text").html(this.options.label).appendTo(f.empty()).text(),c=this.options.icons,h=c.primary&&c.secondary,i=[];if(c.primary||c.secondary){if(this.options.text)i.push("ui-button-text-icon"+(h?"s":c.primary?"-primary":"-secondary"));c.primary&&f.prepend("");c.secondary&&f.append("");if(!this.options.text){i.push(h?"ui-button-icons-only": +"ui-button-icon-only");this.hasTitle||f.attr("title",a)}}else i.push("ui-button-text-only");f.addClass(i.join(" "))}}});b.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(f,a){f==="disabled"&&this.buttons.button("option",f,a);b.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return b(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass("ui-corner-left").end().filter(":last").addClass("ui-corner-right").end().end()}, +destroy:function(){this.element.removeClass("ui-buttonset");this.buttons.map(function(){return b(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy");b.Widget.prototype.destroy.call(this)}})})(jQuery); +(function(b,d){function e(){this.debug=false;this._curInst=null;this._keyEvent=false;this._disabledInputs=[];this._inDialog=this._datepickerShowing=false;this._mainDivId="ui-datepicker-div";this._inlineClass="ui-datepicker-inline";this._appendClass="ui-datepicker-append";this._triggerClass="ui-datepicker-trigger";this._dialogClass="ui-datepicker-dialog";this._disableClass="ui-datepicker-disabled";this._unselectableClass="ui-datepicker-unselectable";this._currentClass="ui-datepicker-current-day";this._dayOverClass= +"ui-datepicker-days-cell-over";this.regional=[];this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su", +"Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:false,showMonthAfterYear:false,yearSuffix:""};this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:false,hideIfNoPrevNext:false,navigationAsDateFormat:false,gotoCurrent:false,changeMonth:false,changeYear:false,yearRange:"c-10:c+10",showOtherMonths:false,selectOtherMonths:false,showWeek:false,calculateWeek:this.iso8601Week,shortYearCutoff:"+10", +minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:true,showButtonPanel:false,autoSize:false};b.extend(this._defaults,this.regional[""]);this.dpDiv=b('
        ')}function g(a,c){b.extend(a,c);for(var h in c)if(c[h]== +null||c[h]==d)a[h]=c[h];return a}b.extend(b.ui,{datepicker:{version:"1.8.11"}});var f=(new Date).getTime();b.extend(e.prototype,{markerClassName:"hasDatepicker",log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){g(this._defaults,a||{});return this},_attachDatepicker:function(a,c){var h=null;for(var i in this._defaults){var j=a.getAttribute("date:"+i);if(j){h=h||{};try{h[i]=eval(j)}catch(n){h[i]=j}}}i=a.nodeName.toLowerCase(); +j=i=="div"||i=="span";if(!a.id){this.uuid+=1;a.id="dp"+this.uuid}var p=this._newInst(b(a),j);p.settings=b.extend({},c||{},h||{});if(i=="input")this._connectDatepicker(a,p);else j&&this._inlineDatepicker(a,p)},_newInst:function(a,c){return{id:a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1"),input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:c,dpDiv:!c?this.dpDiv:b('
        ')}}, +_connectDatepicker:function(a,c){var h=b(a);c.append=b([]);c.trigger=b([]);if(!h.hasClass(this.markerClassName)){this._attachments(h,c);h.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(i,j,n){c.settings[j]=n}).bind("getData.datepicker",function(i,j){return this._get(c,j)});this._autoSize(c);b.data(a,"datepicker",c)}},_attachments:function(a,c){var h=this._get(c,"appendText"),i=this._get(c,"isRTL");c.append&& +c.append.remove();if(h){c.append=b(''+h+"");a[i?"before":"after"](c.append)}a.unbind("focus",this._showDatepicker);c.trigger&&c.trigger.remove();h=this._get(c,"showOn");if(h=="focus"||h=="both")a.focus(this._showDatepicker);if(h=="button"||h=="both"){h=this._get(c,"buttonText");var j=this._get(c,"buttonImage");c.trigger=b(this._get(c,"buttonImageOnly")?b("").addClass(this._triggerClass).attr({src:j,alt:h,title:h}):b('').addClass(this._triggerClass).html(j== +""?h:b("").attr({src:j,alt:h,title:h})));a[i?"before":"after"](c.trigger);c.trigger.click(function(){b.datepicker._datepickerShowing&&b.datepicker._lastInput==a[0]?b.datepicker._hideDatepicker():b.datepicker._showDatepicker(a[0]);return false})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var c=new Date(2009,11,20),h=this._get(a,"dateFormat");if(h.match(/[DM]/)){var i=function(j){for(var n=0,p=0,l=0;ln){n=j[l].length;p=l}return p};c.setMonth(i(this._get(a, +h.match(/MM/)?"monthNames":"monthNamesShort")));c.setDate(i(this._get(a,h.match(/DD/)?"dayNames":"dayNamesShort"))+20-c.getDay())}a.input.attr("size",this._formatDate(a,c).length)}},_inlineDatepicker:function(a,c){var h=b(a);if(!h.hasClass(this.markerClassName)){h.addClass(this.markerClassName).append(c.dpDiv).bind("setData.datepicker",function(i,j,n){c.settings[j]=n}).bind("getData.datepicker",function(i,j){return this._get(c,j)});b.data(a,"datepicker",c);this._setDate(c,this._getDefaultDate(c), +true);this._updateDatepicker(c);this._updateAlternate(c);c.dpDiv.show()}},_dialogDatepicker:function(a,c,h,i,j){a=this._dialogInst;if(!a){this.uuid+=1;this._dialogInput=b('');this._dialogInput.keydown(this._doKeyDown);b("body").append(this._dialogInput);a=this._dialogInst=this._newInst(this._dialogInput,false);a.settings={};b.data(this._dialogInput[0],"datepicker",a)}g(a.settings,i||{}); +c=c&&c.constructor==Date?this._formatDate(a,c):c;this._dialogInput.val(c);this._pos=j?j.length?j:[j.pageX,j.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px");a.settings.onSelect=h;this._inDialog=true;this.dpDiv.addClass(this._dialogClass); +this._showDatepicker(this._dialogInput[0]);b.blockUI&&b.blockUI(this.dpDiv);b.data(this._dialogInput[0],"datepicker",a);return this},_destroyDatepicker:function(a){var c=b(a),h=b.data(a,"datepicker");if(c.hasClass(this.markerClassName)){var i=a.nodeName.toLowerCase();b.removeData(a,"datepicker");if(i=="input"){h.append.remove();h.trigger.remove();c.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup", +this._doKeyUp)}else if(i=="div"||i=="span")c.removeClass(this.markerClassName).empty()}},_enableDatepicker:function(a){var c=b(a),h=b.data(a,"datepicker");if(c.hasClass(this.markerClassName)){var i=a.nodeName.toLowerCase();if(i=="input"){a.disabled=false;h.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(i=="div"||i=="span")c.children("."+this._inlineClass).children().removeClass("ui-state-disabled");this._disabledInputs=b.map(this._disabledInputs, +function(j){return j==a?null:j})}},_disableDatepicker:function(a){var c=b(a),h=b.data(a,"datepicker");if(c.hasClass(this.markerClassName)){var i=a.nodeName.toLowerCase();if(i=="input"){a.disabled=true;h.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else if(i=="div"||i=="span")c.children("."+this._inlineClass).children().addClass("ui-state-disabled");this._disabledInputs=b.map(this._disabledInputs,function(j){return j==a?null: +j});this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return false;for(var c=0;c-1}},_doKeyUp:function(a){a=b.datepicker._getInst(a.target); +if(a.input.val()!=a.lastVal)try{if(b.datepicker.parseDate(b.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,b.datepicker._getFormatConfig(a))){b.datepicker._setDateFromField(a);b.datepicker._updateAlternate(a);b.datepicker._updateDatepicker(a)}}catch(c){b.datepicker.log(c)}return true},_showDatepicker:function(a){a=a.target||a;if(a.nodeName.toLowerCase()!="input")a=b("input",a.parentNode)[0];if(!(b.datepicker._isDisabledDatepicker(a)||b.datepicker._lastInput==a)){var c=b.datepicker._getInst(a); +b.datepicker._curInst&&b.datepicker._curInst!=c&&b.datepicker._curInst.dpDiv.stop(true,true);var h=b.datepicker._get(c,"beforeShow");g(c.settings,h?h.apply(a,[a,c]):{});c.lastVal=null;b.datepicker._lastInput=a;b.datepicker._setDateFromField(c);if(b.datepicker._inDialog)a.value="";if(!b.datepicker._pos){b.datepicker._pos=b.datepicker._findPos(a);b.datepicker._pos[1]+=a.offsetHeight}var i=false;b(a).parents().each(function(){i|=b(this).css("position")=="fixed";return!i});if(i&&b.browser.opera){b.datepicker._pos[0]-= +document.documentElement.scrollLeft;b.datepicker._pos[1]-=document.documentElement.scrollTop}h={left:b.datepicker._pos[0],top:b.datepicker._pos[1]};b.datepicker._pos=null;c.dpDiv.empty();c.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});b.datepicker._updateDatepicker(c);h=b.datepicker._checkOffset(c,h,i);c.dpDiv.css({position:b.datepicker._inDialog&&b.blockUI?"static":i?"fixed":"absolute",display:"none",left:h.left+"px",top:h.top+"px"});if(!c.inline){h=b.datepicker._get(c,"showAnim"); +var j=b.datepicker._get(c,"duration"),n=function(){b.datepicker._datepickerShowing=true;var p=c.dpDiv.find("iframe.ui-datepicker-cover");if(p.length){var l=b.datepicker._getBorders(c.dpDiv);p.css({left:-l[0],top:-l[1],width:c.dpDiv.outerWidth(),height:c.dpDiv.outerHeight()})}};c.dpDiv.zIndex(b(a).zIndex()+1);b.effects&&b.effects[h]?c.dpDiv.show(h,b.datepicker._get(c,"showOptions"),j,n):c.dpDiv[h||"show"](h?j:null,n);if(!h||!j)n();c.input.is(":visible")&&!c.input.is(":disabled")&&c.input.focus();b.datepicker._curInst= +c}}},_updateDatepicker:function(a){var c=this,h=b.datepicker._getBorders(a.dpDiv);a.dpDiv.empty().append(this._generateHTML(a));var i=a.dpDiv.find("iframe.ui-datepicker-cover");i.length&&i.css({left:-h[0],top:-h[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()});a.dpDiv.find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout",function(){b(this).removeClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!=-1&&b(this).removeClass("ui-datepicker-prev-hover"); +this.className.indexOf("ui-datepicker-next")!=-1&&b(this).removeClass("ui-datepicker-next-hover")}).bind("mouseover",function(){if(!c._isDisabledDatepicker(a.inline?a.dpDiv.parent()[0]:a.input[0])){b(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");b(this).addClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!=-1&&b(this).addClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&b(this).addClass("ui-datepicker-next-hover")}}).end().find("."+ +this._dayOverClass+" a").trigger("mouseover").end();h=this._getNumberOfMonths(a);i=h[1];i>1?a.dpDiv.addClass("ui-datepicker-multi-"+i).css("width",17*i+"em"):a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");a.dpDiv[(h[0]!=1||h[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");a==b.datepicker._curInst&&b.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&& +a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var j=a.yearshtml;setTimeout(function(){j===a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml);j=a.yearshtml=null},0)}},_getBorders:function(a){var c=function(h){return{thin:1,medium:2,thick:3}[h]||h};return[parseFloat(c(a.css("border-left-width"))),parseFloat(c(a.css("border-top-width")))]},_checkOffset:function(a,c,h){var i=a.dpDiv.outerWidth(),j=a.dpDiv.outerHeight(),n=a.input?a.input.outerWidth(): +0,p=a.input?a.input.outerHeight():0,l=document.documentElement.clientWidth+b(document).scrollLeft(),k=document.documentElement.clientHeight+b(document).scrollTop();c.left-=this._get(a,"isRTL")?i-n:0;c.left-=h&&c.left==a.input.offset().left?b(document).scrollLeft():0;c.top-=h&&c.top==a.input.offset().top+p?b(document).scrollTop():0;c.left-=Math.min(c.left,c.left+i>l&&l>i?Math.abs(c.left+i-l):0);c.top-=Math.min(c.top,c.top+j>k&&k>j?Math.abs(j+p):0);return c},_findPos:function(a){for(var c=this._get(this._getInst(a), +"isRTL");a&&(a.type=="hidden"||a.nodeType!=1||b.expr.filters.hidden(a));)a=a[c?"previousSibling":"nextSibling"];a=b(a).offset();return[a.left,a.top]},_hideDatepicker:function(a){var c=this._curInst;if(!(!c||a&&c!=b.data(a,"datepicker")))if(this._datepickerShowing){a=this._get(c,"showAnim");var h=this._get(c,"duration"),i=function(){b.datepicker._tidyDialog(c);this._curInst=null};b.effects&&b.effects[a]?c.dpDiv.hide(a,b.datepicker._get(c,"showOptions"),h,i):c.dpDiv[a=="slideDown"?"slideUp":a=="fadeIn"? +"fadeOut":"hide"](a?h:null,i);a||i();if(a=this._get(c,"onClose"))a.apply(c.input?c.input[0]:null,[c.input?c.input.val():"",c]);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(b.blockUI){b.unblockUI();b("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(b.datepicker._curInst){a= +b(a.target);a[0].id!=b.datepicker._mainDivId&&a.parents("#"+b.datepicker._mainDivId).length==0&&!a.hasClass(b.datepicker.markerClassName)&&!a.hasClass(b.datepicker._triggerClass)&&b.datepicker._datepickerShowing&&!(b.datepicker._inDialog&&b.blockUI)&&b.datepicker._hideDatepicker()}},_adjustDate:function(a,c,h){a=b(a);var i=this._getInst(a[0]);if(!this._isDisabledDatepicker(a[0])){this._adjustInstDate(i,c+(h=="M"?this._get(i,"showCurrentAtPos"):0),h);this._updateDatepicker(i)}},_gotoToday:function(a){a= +b(a);var c=this._getInst(a[0]);if(this._get(c,"gotoCurrent")&&c.currentDay){c.selectedDay=c.currentDay;c.drawMonth=c.selectedMonth=c.currentMonth;c.drawYear=c.selectedYear=c.currentYear}else{var h=new Date;c.selectedDay=h.getDate();c.drawMonth=c.selectedMonth=h.getMonth();c.drawYear=c.selectedYear=h.getFullYear()}this._notifyChange(c);this._adjustDate(a)},_selectMonthYear:function(a,c,h){a=b(a);var i=this._getInst(a[0]);i._selectingMonthYear=false;i["selected"+(h=="M"?"Month":"Year")]=i["draw"+(h== +"M"?"Month":"Year")]=parseInt(c.options[c.selectedIndex].value,10);this._notifyChange(i);this._adjustDate(a)},_clickMonthYear:function(a){var c=this._getInst(b(a)[0]);c.input&&c._selectingMonthYear&&setTimeout(function(){c.input.focus()},0);c._selectingMonthYear=!c._selectingMonthYear},_selectDay:function(a,c,h,i){var j=b(a);if(!(b(i).hasClass(this._unselectableClass)||this._isDisabledDatepicker(j[0]))){j=this._getInst(j[0]);j.selectedDay=j.currentDay=b("a",i).html();j.selectedMonth=j.currentMonth= +c;j.selectedYear=j.currentYear=h;this._selectDate(a,this._formatDate(j,j.currentDay,j.currentMonth,j.currentYear))}},_clearDate:function(a){a=b(a);this._getInst(a[0]);this._selectDate(a,"")},_selectDate:function(a,c){a=this._getInst(b(a)[0]);c=c!=null?c:this._formatDate(a);a.input&&a.input.val(c);this._updateAlternate(a);var h=this._get(a,"onSelect");if(h)h.apply(a.input?a.input[0]:null,[c,a]);else a.input&&a.input.trigger("change");if(a.inline)this._updateDatepicker(a);else{this._hideDatepicker(); +this._lastInput=a.input[0];typeof a.input[0]!="object"&&a.input.focus();this._lastInput=null}},_updateAlternate:function(a){var c=this._get(a,"altField");if(c){var h=this._get(a,"altFormat")||this._get(a,"dateFormat"),i=this._getDate(a),j=this.formatDate(h,i,this._getFormatConfig(a));b(c).each(function(){b(this).val(j)})}},noWeekends:function(a){a=a.getDay();return[a>0&&a<6,""]},iso8601Week:function(a){a=new Date(a.getTime());a.setDate(a.getDate()+4-(a.getDay()||7));var c=a.getTime();a.setMonth(0); +a.setDate(1);return Math.floor(Math.round((c-a)/864E5)/7)+1},parseDate:function(a,c,h){if(a==null||c==null)throw"Invalid arguments";c=typeof c=="object"?c.toString():c+"";if(c=="")return null;var i=(h?h.shortYearCutoff:null)||this._defaults.shortYearCutoff;i=typeof i!="string"?i:(new Date).getFullYear()%100+parseInt(i,10);for(var j=(h?h.dayNamesShort:null)||this._defaults.dayNamesShort,n=(h?h.dayNames:null)||this._defaults.dayNames,p=(h?h.monthNamesShort:null)||this._defaults.monthNamesShort,l=(h? +h.monthNames:null)||this._defaults.monthNames,k=h=-1,m=-1,o=-1,q=false,s=function(x){(x=y+1-1){k=1;m=o;do{i=this._getDaysInMonth(h,k-1);if(m<=i)break;k++;m-=i}while(1)}B=this._daylightSavingAdjust(new Date(h,k-1,m));if(B.getFullYear()!=h||B.getMonth()+1!=k||B.getDate()!=m)throw"Invalid date";return B},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y", +RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1E7,formatDate:function(a,c,h){if(!c)return"";var i=(h?h.dayNamesShort:null)||this._defaults.dayNamesShort,j=(h?h.dayNames:null)||this._defaults.dayNames,n=(h?h.monthNamesShort:null)||this._defaults.monthNamesShort;h=(h?h.monthNames:null)||this._defaults.monthNames;var p=function(s){(s=q+112?a.getHours()+2:0);return a},_setDate:function(a,c,h){var i=!c,j=a.selectedMonth,n=a.selectedYear;c=this._restrictMinMax(a,this._determineDate(a,c,new Date));a.selectedDay= +a.currentDay=c.getDate();a.drawMonth=a.selectedMonth=a.currentMonth=c.getMonth();a.drawYear=a.selectedYear=a.currentYear=c.getFullYear();if((j!=a.selectedMonth||n!=a.selectedYear)&&!h)this._notifyChange(a);this._adjustInstDate(a);if(a.input)a.input.val(i?"":this._formatDate(a))},_getDate:function(a){return!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay))},_generateHTML:function(a){var c=new Date;c=this._daylightSavingAdjust(new Date(c.getFullYear(), +c.getMonth(),c.getDate()));var h=this._get(a,"isRTL"),i=this._get(a,"showButtonPanel"),j=this._get(a,"hideIfNoPrevNext"),n=this._get(a,"navigationAsDateFormat"),p=this._getNumberOfMonths(a),l=this._get(a,"showCurrentAtPos"),k=this._get(a,"stepMonths"),m=p[0]!=1||p[1]!=1,o=this._daylightSavingAdjust(!a.currentDay?new Date(9999,9,9):new Date(a.currentYear,a.currentMonth,a.currentDay)),q=this._getMinMaxDate(a,"min"),s=this._getMinMaxDate(a,"max");l=a.drawMonth-l;var r=a.drawYear;if(l<0){l+=12;r--}if(s){var u= +this._daylightSavingAdjust(new Date(s.getFullYear(),s.getMonth()-p[0]*p[1]+1,s.getDate()));for(u=q&&uu;){l--;if(l<0){l=11;r--}}}a.drawMonth=l;a.drawYear=r;u=this._get(a,"prevText");u=!n?u:this.formatDate(u,this._daylightSavingAdjust(new Date(r,l-k,1)),this._getFormatConfig(a));u=this._canAdjustMonth(a,-1,r,l)?''+u+"":j?"":''+u+"";var v=this._get(a,"nextText");v=!n?v:this.formatDate(v,this._daylightSavingAdjust(new Date(r,l+k,1)),this._getFormatConfig(a));j=this._canAdjustMonth(a,+1,r,l)?''+v+"":j?"":''+v+"";k=this._get(a,"currentText");v=this._get(a,"gotoCurrent")&&a.currentDay?o:c;k=!n?k:this.formatDate(k,v,this._getFormatConfig(a));n=!a.inline?'":"";i=i?'
        '+(h?n:"")+(this._isInRange(a,v)?'":"")+(h?"":n)+"
        ":"";n=parseInt(this._get(a,"firstDay"),10);n=isNaN(n)?0:n;k=this._get(a,"showWeek");v=this._get(a,"dayNames");this._get(a,"dayNamesShort");var w=this._get(a,"dayNamesMin"),y= +this._get(a,"monthNames"),B=this._get(a,"monthNamesShort"),x=this._get(a,"beforeShowDay"),C=this._get(a,"showOtherMonths"),J=this._get(a,"selectOtherMonths");this._get(a,"calculateWeek");for(var M=this._getDefaultDate(a),K="",G=0;G1)switch(H){case 0:D+=" ui-datepicker-group-first";A=" ui-corner-"+(h?"right":"left");break;case p[1]- +1:D+=" ui-datepicker-group-last";A=" ui-corner-"+(h?"left":"right");break;default:D+=" ui-datepicker-group-middle";A="";break}D+='">'}D+='
        '+(/all|left/.test(A)&&G==0?h?j:u:"")+(/all|right/.test(A)&&G==0?h?u:j:"")+this._generateMonthYearHeader(a,l,r,q,s,G>0||H>0,y,B)+'
        ';var E=k?'":"";for(A=0;A<7;A++){var z= +(A+n)%7;E+="=5?' class="ui-datepicker-week-end"':"")+'>'+w[z]+""}D+=E+"";E=this._getDaysInMonth(r,l);if(r==a.selectedYear&&l==a.selectedMonth)a.selectedDay=Math.min(a.selectedDay,E);A=(this._getFirstDayOfMonth(r,l)-n+7)%7;E=m?6:Math.ceil((A+E)/7);z=this._daylightSavingAdjust(new Date(r,l,1-A));for(var P=0;P";var Q=!k?"":'";for(A=0;A<7;A++){var I= +x?x.apply(a.input?a.input[0]:null,[z]):[true,""],F=z.getMonth()!=l,L=F&&!J||!I[0]||q&&zs;Q+='";z.setDate(z.getDate()+1);z=this._daylightSavingAdjust(z)}D+= +Q+""}l++;if(l>11){l=0;r++}D+="
        '+this._get(a,"weekHeader")+"
        '+this._get(a,"calculateWeek")(z)+""+(F&&!C?" ":L?''+z.getDate()+"":''+z.getDate()+"")+"
        "+(m?""+(p[0]>0&&H==p[1]-1?'
        ':""):"");N+=D}K+=N}K+=i+(b.browser.msie&&parseInt(b.browser.version,10)<7&&!a.inline?'':"");a._keyEvent=false;return K},_generateMonthYearHeader:function(a,c,h,i,j,n,p,l){var k=this._get(a,"changeMonth"),m=this._get(a,"changeYear"),o=this._get(a,"showMonthAfterYear"),q='
        ', +s="";if(n||!k)s+=''+p[c]+"";else{p=i&&i.getFullYear()==h;var r=j&&j.getFullYear()==h;s+='"}o||(q+=s+(n||!(k&& +m)?" ":""));a.yearshtml="";if(n||!m)q+=''+h+"";else{l=this._get(a,"yearRange").split(":");var v=(new Date).getFullYear();p=function(w){w=w.match(/c[+-].*/)?h+parseInt(w.substring(1),10):w.match(/[+-].*/)?v+parseInt(w,10):parseInt(w,10);return isNaN(w)?v:w};c=p(l[0]);l=Math.max(c,p(l[1]||""));c=i?Math.max(c,i.getFullYear()):c;l=j?Math.min(l,j.getFullYear()):l;for(a.yearshtml+='";if(b.browser.mozilla)q+='";else{q+=a.yearshtml;a.yearshtml=null}}q+=this._get(a,"yearSuffix");if(o)q+=(n||!(k&&m)?" ":"")+s;q+="
        ";return q},_adjustInstDate:function(a,c,h){var i= +a.drawYear+(h=="Y"?c:0),j=a.drawMonth+(h=="M"?c:0);c=Math.min(a.selectedDay,this._getDaysInMonth(i,j))+(h=="D"?c:0);i=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(i,j,c)));a.selectedDay=i.getDate();a.drawMonth=a.selectedMonth=i.getMonth();a.drawYear=a.selectedYear=i.getFullYear();if(h=="M"||h=="Y")this._notifyChange(a)},_restrictMinMax:function(a,c){var h=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");c=h&&ca?a:c},_notifyChange:function(a){var c=this._get(a, +"onChangeMonthYear");if(c)c.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return a==null?[1,1]:typeof a=="number"?[1,a]:a},_getMinMaxDate:function(a,c){return this._determineDate(a,this._get(a,c+"Date"),null)},_getDaysInMonth:function(a,c){return 32-this._daylightSavingAdjust(new Date(a,c,32)).getDate()},_getFirstDayOfMonth:function(a,c){return(new Date(a,c,1)).getDay()},_canAdjustMonth:function(a,c,h,i){var j=this._getNumberOfMonths(a); +h=this._daylightSavingAdjust(new Date(h,i+(c<0?c:j[0]*j[1]),1));c<0&&h.setDate(this._getDaysInMonth(h.getFullYear(),h.getMonth()));return this._isInRange(a,h)},_isInRange:function(a,c){var h=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");return(!h||c.getTime()>=h.getTime())&&(!a||c.getTime()<=a.getTime())},_getFormatConfig:function(a){var c=this._get(a,"shortYearCutoff");c=typeof c!="string"?c:(new Date).getFullYear()%100+parseInt(c,10);return{shortYearCutoff:c,dayNamesShort:this._get(a, +"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,c,h,i){if(!c){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}c=c?typeof c=="object"?c:this._daylightSavingAdjust(new Date(i,h,c)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),c,this._getFormatConfig(a))}});b.fn.datepicker= +function(a){if(!this.length)return this;if(!b.datepicker.initialized){b(document).mousedown(b.datepicker._checkExternalClick).find("body").append(b.datepicker.dpDiv);b.datepicker.initialized=true}var c=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return b.datepicker["_"+a+"Datepicker"].apply(b.datepicker,[this[0]].concat(c));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return b.datepicker["_"+a+"Datepicker"].apply(b.datepicker, +[this[0]].concat(c));return this.each(function(){typeof a=="string"?b.datepicker["_"+a+"Datepicker"].apply(b.datepicker,[this].concat(c)):b.datepicker._attachDatepicker(this,a)})};b.datepicker=new e;b.datepicker.initialized=false;b.datepicker.uuid=(new Date).getTime();b.datepicker.version="1.8.11";window["DP_jQuery_"+f]=b})(jQuery); +(function(b,d){var e={buttons:true,height:true,maxHeight:true,maxWidth:true,minHeight:true,minWidth:true,width:true},g={maxHeight:true,maxWidth:true,minHeight:true,minWidth:true};b.widget("ui.dialog",{options:{autoOpen:true,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,position:{my:"center",at:"center",collision:"fit",using:function(f){var a=b(this).css(f).offset().top;a<0&& +b(this).css("top",f.top-a)}},resizable:true,show:null,stack:true,title:"",width:300,zIndex:1E3},_create:function(){this.originalTitle=this.element.attr("title");if(typeof this.originalTitle!=="string")this.originalTitle="";this.options.title=this.options.title||this.originalTitle;var f=this,a=f.options,c=a.title||" ",h=b.ui.dialog.getTitleId(f.element),i=(f.uiDialog=b("
        ")).appendTo(document.body).hide().addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+a.dialogClass).css({zIndex:a.zIndex}).attr("tabIndex", +-1).css("outline",0).keydown(function(p){if(a.closeOnEscape&&p.keyCode&&p.keyCode===b.ui.keyCode.ESCAPE){f.close(p);p.preventDefault()}}).attr({role:"dialog","aria-labelledby":h}).mousedown(function(p){f.moveToTop(false,p)});f.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(i);var j=(f.uiDialogTitlebar=b("
        ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(i),n=b('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role", +"button").hover(function(){n.addClass("ui-state-hover")},function(){n.removeClass("ui-state-hover")}).focus(function(){n.addClass("ui-state-focus")}).blur(function(){n.removeClass("ui-state-focus")}).click(function(p){f.close(p);return false}).appendTo(j);(f.uiDialogTitlebarCloseText=b("")).addClass("ui-icon ui-icon-closethick").text(a.closeText).appendTo(n);b("").addClass("ui-dialog-title").attr("id",h).html(c).prependTo(j);if(b.isFunction(a.beforeclose)&&!b.isFunction(a.beforeClose))a.beforeClose= +a.beforeclose;j.find("*").add(j).disableSelection();a.draggable&&b.fn.draggable&&f._makeDraggable();a.resizable&&b.fn.resizable&&f._makeResizable();f._createButtons(a.buttons);f._isOpen=false;b.fn.bgiframe&&i.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var f=this;f.overlay&&f.overlay.destroy();f.uiDialog.hide();f.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body");f.uiDialog.remove();f.originalTitle&& +f.element.attr("title",f.originalTitle);return f},widget:function(){return this.uiDialog},close:function(f){var a=this,c,h;if(false!==a._trigger("beforeClose",f)){a.overlay&&a.overlay.destroy();a.uiDialog.unbind("keypress.ui-dialog");a._isOpen=false;if(a.options.hide)a.uiDialog.hide(a.options.hide,function(){a._trigger("close",f)});else{a.uiDialog.hide();a._trigger("close",f)}b.ui.dialog.overlay.resize();if(a.options.modal){c=0;b(".ui-dialog").each(function(){if(this!==a.uiDialog[0]){h=b(this).css("z-index"); +isNaN(h)||(c=Math.max(c,h))}});b.ui.dialog.maxZ=c}return a}},isOpen:function(){return this._isOpen},moveToTop:function(f,a){var c=this,h=c.options;if(h.modal&&!f||!h.stack&&!h.modal)return c._trigger("focus",a);if(h.zIndex>b.ui.dialog.maxZ)b.ui.dialog.maxZ=h.zIndex;if(c.overlay){b.ui.dialog.maxZ+=1;c.overlay.$el.css("z-index",b.ui.dialog.overlay.maxZ=b.ui.dialog.maxZ)}f={scrollTop:c.element.attr("scrollTop"),scrollLeft:c.element.attr("scrollLeft")};b.ui.dialog.maxZ+=1;c.uiDialog.css("z-index",b.ui.dialog.maxZ); +c.element.attr(f);c._trigger("focus",a);return c},open:function(){if(!this._isOpen){var f=this,a=f.options,c=f.uiDialog;f.overlay=a.modal?new b.ui.dialog.overlay(f):null;f._size();f._position(a.position);c.show(a.show);f.moveToTop(true);a.modal&&c.bind("keypress.ui-dialog",function(h){if(h.keyCode===b.ui.keyCode.TAB){var i=b(":tabbable",this),j=i.filter(":first");i=i.filter(":last");if(h.target===i[0]&&!h.shiftKey){j.focus(1);return false}else if(h.target===j[0]&&h.shiftKey){i.focus(1);return false}}}); +b(f.element.find(":tabbable").get().concat(c.find(".ui-dialog-buttonpane :tabbable").get().concat(c.get()))).eq(0).focus();f._isOpen=true;f._trigger("open");return f}},_createButtons:function(f){var a=this,c=false,h=b("
        ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),i=b("
        ").addClass("ui-dialog-buttonset").appendTo(h);a.uiDialog.find(".ui-dialog-buttonpane").remove();typeof f==="object"&&f!==null&&b.each(f,function(){return!(c=true)});if(c){b.each(f,function(j, +n){n=b.isFunction(n)?{click:n,text:j}:n;j=b('').attr(n,true).unbind("click").click(function(){n.click.apply(a.element[0],arguments)}).appendTo(i);b.fn.button&&j.button()});h.appendTo(a.uiDialog)}},_makeDraggable:function(){function f(j){return{position:j.position,offset:j.offset}}var a=this,c=a.options,h=b(document),i;a.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(j,n){i= +c.height==="auto"?"auto":b(this).height();b(this).height(b(this).height()).addClass("ui-dialog-dragging");a._trigger("dragStart",j,f(n))},drag:function(j,n){a._trigger("drag",j,f(n))},stop:function(j,n){c.position=[n.position.left-h.scrollLeft(),n.position.top-h.scrollTop()];b(this).removeClass("ui-dialog-dragging").height(i);a._trigger("dragStop",j,f(n));b.ui.dialog.overlay.resize()}})},_makeResizable:function(f){function a(j){return{originalPosition:j.originalPosition,originalSize:j.originalSize, +position:j.position,size:j.size}}f=f===d?this.options.resizable:f;var c=this,h=c.options,i=c.uiDialog.css("position");f=typeof f==="string"?f:"n,e,s,w,se,sw,ne,nw";c.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:c.element,maxWidth:h.maxWidth,maxHeight:h.maxHeight,minWidth:h.minWidth,minHeight:c._minHeight(),handles:f,start:function(j,n){b(this).addClass("ui-dialog-resizing");c._trigger("resizeStart",j,a(n))},resize:function(j,n){c._trigger("resize",j,a(n))},stop:function(j, +n){b(this).removeClass("ui-dialog-resizing");h.height=b(this).height();h.width=b(this).width();c._trigger("resizeStop",j,a(n));b.ui.dialog.overlay.resize()}}).css("position",i).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var f=this.options;return f.height==="auto"?f.minHeight:Math.min(f.minHeight,f.height)},_position:function(f){var a=[],c=[0,0],h;if(f){if(typeof f==="string"||typeof f==="object"&&"0"in f){a=f.split?f.split(" "):[f[0],f[1]];if(a.length=== +1)a[1]=a[0];b.each(["left","top"],function(i,j){if(+a[i]===a[i]){c[i]=a[i];a[i]=j}});f={my:a.join(" "),at:a.join(" "),offset:c.join(" ")}}f=b.extend({},b.ui.dialog.prototype.options.position,f)}else f=b.ui.dialog.prototype.options.position;(h=this.uiDialog.is(":visible"))||this.uiDialog.show();this.uiDialog.css({top:0,left:0}).position(b.extend({of:window},f));h||this.uiDialog.hide()},_setOptions:function(f){var a=this,c={},h=false;b.each(f,function(i,j){a._setOption(i,j);if(i in e)h=true;if(i in +g)c[i]=j});h&&this._size();this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",c)},_setOption:function(f,a){var c=this,h=c.uiDialog;switch(f){case "beforeclose":f="beforeClose";break;case "buttons":c._createButtons(a);break;case "closeText":c.uiDialogTitlebarCloseText.text(""+a);break;case "dialogClass":h.removeClass(c.options.dialogClass).addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+a);break;case "disabled":a?h.addClass("ui-dialog-disabled"):h.removeClass("ui-dialog-disabled"); +break;case "draggable":var i=h.is(":data(draggable)");i&&!a&&h.draggable("destroy");!i&&a&&c._makeDraggable();break;case "position":c._position(a);break;case "resizable":(i=h.is(":data(resizable)"))&&!a&&h.resizable("destroy");i&&typeof a==="string"&&h.resizable("option","handles",a);!i&&a!==false&&c._makeResizable(a);break;case "title":b(".ui-dialog-title",c.uiDialogTitlebar).html(""+(a||" "));break}b.Widget.prototype._setOption.apply(c,arguments)},_size:function(){var f=this.options,a,c,h= +this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0});if(f.minWidth>f.width)f.width=f.minWidth;a=this.uiDialog.css({height:"auto",width:f.width}).height();c=Math.max(0,f.minHeight-a);if(f.height==="auto")if(b.support.minHeight)this.element.css({minHeight:c,height:"auto"});else{this.uiDialog.show();f=this.element.css("height","auto").height();h||this.uiDialog.hide();this.element.height(Math.max(f,c))}else this.element.height(Math.max(f.height-a,0));this.uiDialog.is(":data(resizable)")&& +this.uiDialog.resizable("option","minHeight",this._minHeight())}});b.extend(b.ui.dialog,{version:"1.8.11",uuid:0,maxZ:0,getTitleId:function(f){f=f.attr("id");if(!f){this.uuid+=1;f=this.uuid}return"ui-dialog-title-"+f},overlay:function(f){this.$el=b.ui.dialog.overlay.create(f)}});b.extend(b.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:b.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(f){return f+".dialog-overlay"}).join(" "),create:function(f){if(this.instances.length=== +0){setTimeout(function(){b.ui.dialog.overlay.instances.length&&b(document).bind(b.ui.dialog.overlay.events,function(c){if(b(c.target).zIndex()").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(), +height:this.height()});b.fn.bgiframe&&a.bgiframe();this.instances.push(a);return a},destroy:function(f){var a=b.inArray(f,this.instances);a!=-1&&this.oldInstances.push(this.instances.splice(a,1)[0]);this.instances.length===0&&b([document,window]).unbind(".dialog-overlay");f.remove();var c=0;b.each(this.instances,function(){c=Math.max(c,this.css("z-index"))});this.maxZ=c},height:function(){var f,a;if(b.browser.msie&&b.browser.version<7){f=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight); +a=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);return f0?a.left-h:Math.max(a.left-c.collisionPosition.left,a.left)},top:function(a,c){var h=b(window);h=c.collisionPosition.top+c.collisionHeight-h.height()-h.scrollTop();a.top=h>0?a.top-h:Math.max(a.top-c.collisionPosition.top,a.top)}},flip:{left:function(a,c){if(c.at[0]!=="center"){var h=b(window);h=c.collisionPosition.left+c.collisionWidth-h.width()-h.scrollLeft();var i=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,j=c.at[0]==="left"?c.targetWidth:-c.targetWidth,n=-2*c.offset[0];a.left+= +c.collisionPosition.left<0?i+j+n:h>0?i+j+n:0}},top:function(a,c){if(c.at[1]!=="center"){var h=b(window);h=c.collisionPosition.top+c.collisionHeight-h.height()-h.scrollTop();var i=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,j=c.at[1]==="top"?c.targetHeight:-c.targetHeight,n=-2*c.offset[1];a.top+=c.collisionPosition.top<0?i+j+n:h>0?i+j+n:0}}}};if(!b.offset.setOffset){b.offset.setOffset=function(a,c){if(/static/.test(b.curCSS(a,"position")))a.style.position="relative";var h=b(a), +i=h.offset(),j=parseInt(b.curCSS(a,"top",true),10)||0,n=parseInt(b.curCSS(a,"left",true),10)||0;i={top:c.top-i.top+j,left:c.left-i.left+n};"using"in c?c.using.call(a,i):h.css(i)};b.fn.offset=function(a){var c=this[0];if(!c||!c.ownerDocument)return null;if(a)return this.each(function(){b.offset.setOffset(this,a)});return f.call(this)}}})(jQuery); +(function(b,d){b.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()});this.valueDiv=b("
        ").appendTo(this.element);this.oldValue=this._value();this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"); +this.valueDiv.remove();b.Widget.prototype.destroy.apply(this,arguments)},value:function(e){if(e===d)return this._value();this._setOption("value",e);return this},_setOption:function(e,g){if(e==="value"){this.options.value=g;this._refreshValue();this._value()===this.options.max&&this._trigger("complete")}b.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var e=this.options.value;if(typeof e!=="number")e=0;return Math.min(this.options.max,Math.max(this.min,e))},_percentage:function(){return 100* +this._value()/this.options.max},_refreshValue:function(){var e=this.value(),g=this._percentage();if(this.oldValue!==e){this.oldValue=e;this._trigger("change")}this.valueDiv.toggleClass("ui-corner-right",e===this.options.max).width(g.toFixed(0)+"%");this.element.attr("aria-valuenow",e)}});b.extend(b.ui.progressbar,{version:"1.8.11"})})(jQuery); +(function(b){b.widget("ui.slider",b.ui.mouse,{widgetEventPrefix:"slide",options:{animate:false,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null},_create:function(){var d=this,e=this.options;this._mouseSliding=this._keySliding=false;this._animateOff=true;this._handleIndex=null;this._detectOrientation();this._mouseInit();this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget ui-widget-content ui-corner-all");e.disabled&&this.element.addClass("ui-slider-disabled ui-disabled"); +this.range=b([]);if(e.range){if(e.range===true){this.range=b("
        ");if(!e.values)e.values=[this._valueMin(),this._valueMin()];if(e.values.length&&e.values.length!==2)e.values=[e.values[0],e.values[0]]}else this.range=b("
        ");this.range.appendTo(this.element).addClass("ui-slider-range");if(e.range==="min"||e.range==="max")this.range.addClass("ui-slider-range-"+e.range);this.range.addClass("ui-widget-header")}b(".ui-slider-handle",this.element).length===0&&b("").appendTo(this.element).addClass("ui-slider-handle"); +if(e.values&&e.values.length)for(;b(".ui-slider-handle",this.element).length").appendTo(this.element).addClass("ui-slider-handle");this.handles=b(".ui-slider-handle",this.element).addClass("ui-state-default ui-corner-all");this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(g){g.preventDefault()}).hover(function(){e.disabled||b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")}).focus(function(){if(e.disabled)b(this).blur(); +else{b(".ui-slider .ui-state-focus").removeClass("ui-state-focus");b(this).addClass("ui-state-focus")}}).blur(function(){b(this).removeClass("ui-state-focus")});this.handles.each(function(g){b(this).data("index.ui-slider-handle",g)});this.handles.keydown(function(g){var f=true,a=b(this).data("index.ui-slider-handle"),c,h,i;if(!d.options.disabled){switch(g.keyCode){case b.ui.keyCode.HOME:case b.ui.keyCode.END:case b.ui.keyCode.PAGE_UP:case b.ui.keyCode.PAGE_DOWN:case b.ui.keyCode.UP:case b.ui.keyCode.RIGHT:case b.ui.keyCode.DOWN:case b.ui.keyCode.LEFT:f= +false;if(!d._keySliding){d._keySliding=true;b(this).addClass("ui-state-active");c=d._start(g,a);if(c===false)return}break}i=d.options.step;c=d.options.values&&d.options.values.length?(h=d.values(a)):(h=d.value());switch(g.keyCode){case b.ui.keyCode.HOME:h=d._valueMin();break;case b.ui.keyCode.END:h=d._valueMax();break;case b.ui.keyCode.PAGE_UP:h=d._trimAlignValue(c+(d._valueMax()-d._valueMin())/5);break;case b.ui.keyCode.PAGE_DOWN:h=d._trimAlignValue(c-(d._valueMax()-d._valueMin())/5);break;case b.ui.keyCode.UP:case b.ui.keyCode.RIGHT:if(c=== +d._valueMax())return;h=d._trimAlignValue(c+i);break;case b.ui.keyCode.DOWN:case b.ui.keyCode.LEFT:if(c===d._valueMin())return;h=d._trimAlignValue(c-i);break}d._slide(g,a,h);return f}}).keyup(function(g){var f=b(this).data("index.ui-slider-handle");if(d._keySliding){d._keySliding=false;d._stop(g,f);d._change(g,f);b(this).removeClass("ui-state-active")}});this._refreshValue();this._animateOff=false},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider"); +this._mouseDestroy();return this},_mouseCapture:function(d){var e=this.options,g,f,a,c,h;if(e.disabled)return false;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();g=this._normValueFromMouse({x:d.pageX,y:d.pageY});f=this._valueMax()-this._valueMin()+1;c=this;this.handles.each(function(i){var j=Math.abs(g-c.values(i));if(f>j){f=j;a=b(this);h=i}});if(e.range===true&&this.values(1)===e.min){h+=1;a=b(this.handles[h])}if(this._start(d, +h)===false)return false;this._mouseSliding=true;c._handleIndex=h;a.addClass("ui-state-active").focus();e=a.offset();this._clickOffset=!b(d.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:d.pageX-e.left-a.width()/2,top:d.pageY-e.top-a.height()/2-(parseInt(a.css("borderTopWidth"),10)||0)-(parseInt(a.css("borderBottomWidth"),10)||0)+(parseInt(a.css("marginTop"),10)||0)};this.handles.hasClass("ui-state-hover")||this._slide(d,h,g);return this._animateOff=true},_mouseStart:function(){return true}, +_mouseDrag:function(d){var e=this._normValueFromMouse({x:d.pageX,y:d.pageY});this._slide(d,this._handleIndex,e);return false},_mouseStop:function(d){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(d,this._handleIndex);this._change(d,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=false},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(d){var e; +if(this.orientation==="horizontal"){e=this.elementSize.width;d=d.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{e=this.elementSize.height;d=d.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}e=d/e;if(e>1)e=1;if(e<0)e=0;if(this.orientation==="vertical")e=1-e;d=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+e*d)},_start:function(d,e){var g={handle:this.handles[e],value:this.value()};if(this.options.values&&this.options.values.length){g.value= +this.values(e);g.values=this.values()}return this._trigger("start",d,g)},_slide:function(d,e,g){var f;if(this.options.values&&this.options.values.length){f=this.values(e?0:1);if(this.options.values.length===2&&this.options.range===true&&(e===0&&g>f||e===1&&g1){this.options.values[d]=this._trimAlignValue(e);this._refreshValue();this._change(null,d)}if(arguments.length)if(b.isArray(arguments[0])){g=this.options.values;f=arguments[0];for(a=0;a=this._valueMax())return this._valueMax();var e=this.options.step>0?this.options.step:1,g=(d-this._valueMin())%e;alignValue=d-g;if(Math.abs(g)*2>=e)alignValue+=g>0?e:-e;return parseFloat(alignValue.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max}, +_refreshValue:function(){var d=this.options.range,e=this.options,g=this,f=!this._animateOff?e.animate:false,a,c={},h,i,j,n;if(this.options.values&&this.options.values.length)this.handles.each(function(p){a=(g.values(p)-g._valueMin())/(g._valueMax()-g._valueMin())*100;c[g.orientation==="horizontal"?"left":"bottom"]=a+"%";b(this).stop(1,1)[f?"animate":"css"](c,e.animate);if(g.options.range===true)if(g.orientation==="horizontal"){if(p===0)g.range.stop(1,1)[f?"animate":"css"]({left:a+"%"},e.animate); +if(p===1)g.range[f?"animate":"css"]({width:a-h+"%"},{queue:false,duration:e.animate})}else{if(p===0)g.range.stop(1,1)[f?"animate":"css"]({bottom:a+"%"},e.animate);if(p===1)g.range[f?"animate":"css"]({height:a-h+"%"},{queue:false,duration:e.animate})}h=a});else{i=this.value();j=this._valueMin();n=this._valueMax();a=n!==j?(i-j)/(n-j)*100:0;c[g.orientation==="horizontal"?"left":"bottom"]=a+"%";this.handle.stop(1,1)[f?"animate":"css"](c,e.animate);if(d==="min"&&this.orientation==="horizontal")this.range.stop(1, +1)[f?"animate":"css"]({width:a+"%"},e.animate);if(d==="max"&&this.orientation==="horizontal")this.range[f?"animate":"css"]({width:100-a+"%"},{queue:false,duration:e.animate});if(d==="min"&&this.orientation==="vertical")this.range.stop(1,1)[f?"animate":"css"]({height:a+"%"},e.animate);if(d==="max"&&this.orientation==="vertical")this.range[f?"animate":"css"]({height:100-a+"%"},{queue:false,duration:e.animate})}}});b.extend(b.ui.slider,{version:"1.8.11"})})(jQuery); +(function(b,d){function e(){return++f}function g(){return++a}var f=0,a=0;b.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:false,cookie:null,collapsible:false,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"
        ",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
      • #{label}
      • "},_create:function(){this._tabify(true)},_setOption:function(c,h){if(c=="selected")this.options.collapsible&& +h==this.options.selected||this.select(h);else{this.options[c]=h;this._tabify()}},_tabId:function(c){return c.title&&c.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+e()},_sanitizeSelector:function(c){return c.replace(/:/g,"\\:")},_cookie:function(){var c=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+g());return b.cookie.apply(null,[c].concat(b.makeArray(arguments)))},_ui:function(c,h){return{tab:c,panel:h,index:this.anchors.index(c)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var c= +b(this);c.html(c.data("label.tabs")).removeData("label.tabs")})},_tabify:function(c){function h(r,u){r.css("display","");!b.support.opacity&&u.opacity&&r[0].style.removeAttribute("filter")}var i=this,j=this.options,n=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=b(" > li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return b("a",this)[0]});this.panels=b([]);this.anchors.each(function(r,u){var v=b(u).attr("href"),w=v.split("#")[0],y;if(w&&(w===location.toString().split("#")[0]|| +(y=b("base")[0])&&w===y.href)){v=u.hash;u.href=v}if(n.test(v))i.panels=i.panels.add(i.element.find(i._sanitizeSelector(v)));else if(v&&v!=="#"){b.data(u,"href.tabs",v);b.data(u,"load.tabs",v.replace(/#.*$/,""));v=i._tabId(u);u.href="#"+v;u=i.element.find("#"+v);if(!u.length){u=b(j.panelTemplate).attr("id",v).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(i.panels[r-1]||i.list);u.data("destroy.tabs",true)}i.panels=i.panels.add(u)}else j.disabled.push(r)});if(c){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"); +this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(j.selected===d){location.hash&&this.anchors.each(function(r,u){if(u.hash==location.hash){j.selected=r;return false}});if(typeof j.selected!=="number"&&j.cookie)j.selected=parseInt(i._cookie(),10);if(typeof j.selected!=="number"&&this.lis.filter(".ui-tabs-selected").length)j.selected= +this.lis.index(this.lis.filter(".ui-tabs-selected"));j.selected=j.selected||(this.lis.length?0:-1)}else if(j.selected===null)j.selected=-1;j.selected=j.selected>=0&&this.anchors[j.selected]||j.selected<0?j.selected:0;j.disabled=b.unique(j.disabled.concat(b.map(this.lis.filter(".ui-state-disabled"),function(r){return i.lis.index(r)}))).sort();b.inArray(j.selected,j.disabled)!=-1&&j.disabled.splice(b.inArray(j.selected,j.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active"); +if(j.selected>=0&&this.anchors.length){i.element.find(i._sanitizeSelector(i.anchors[j.selected].hash)).removeClass("ui-tabs-hide");this.lis.eq(j.selected).addClass("ui-tabs-selected ui-state-active");i.element.queue("tabs",function(){i._trigger("show",null,i._ui(i.anchors[j.selected],i.element.find(i._sanitizeSelector(i.anchors[j.selected].hash))[0]))});this.load(j.selected)}b(window).bind("unload",function(){i.lis.add(i.anchors).unbind(".tabs");i.lis=i.anchors=i.panels=null})}else j.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")); +this.element[j.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");j.cookie&&this._cookie(j.selected,j.cookie);c=0;for(var p;p=this.lis[c];c++)b(p)[b.inArray(c,j.disabled)!=-1&&!b(p).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");j.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(j.event!=="mouseover"){var l=function(r,u){u.is(":not(.ui-state-disabled)")&&u.addClass("ui-state-"+r)},k=function(r,u){u.removeClass("ui-state-"+ +r)};this.lis.bind("mouseover.tabs",function(){l("hover",b(this))});this.lis.bind("mouseout.tabs",function(){k("hover",b(this))});this.anchors.bind("focus.tabs",function(){l("focus",b(this).closest("li"))});this.anchors.bind("blur.tabs",function(){k("focus",b(this).closest("li"))})}var m,o;if(j.fx)if(b.isArray(j.fx)){m=j.fx[0];o=j.fx[1]}else m=o=j.fx;var q=o?function(r,u){b(r).closest("li").addClass("ui-tabs-selected ui-state-active");u.hide().removeClass("ui-tabs-hide").animate(o,o.duration||"normal", +function(){h(u,o);i._trigger("show",null,i._ui(r,u[0]))})}:function(r,u){b(r).closest("li").addClass("ui-tabs-selected ui-state-active");u.removeClass("ui-tabs-hide");i._trigger("show",null,i._ui(r,u[0]))},s=m?function(r,u){u.animate(m,m.duration||"normal",function(){i.lis.removeClass("ui-tabs-selected ui-state-active");u.addClass("ui-tabs-hide");h(u,m);i.element.dequeue("tabs")})}:function(r,u){i.lis.removeClass("ui-tabs-selected ui-state-active");u.addClass("ui-tabs-hide");i.element.dequeue("tabs")}; +this.anchors.bind(j.event+".tabs",function(){var r=this,u=b(r).closest("li"),v=i.panels.filter(":not(.ui-tabs-hide)"),w=i.element.find(i._sanitizeSelector(r.hash));if(u.hasClass("ui-tabs-selected")&&!j.collapsible||u.hasClass("ui-state-disabled")||u.hasClass("ui-state-processing")||i.panels.filter(":animated").length||i._trigger("select",null,i._ui(this,w[0]))===false){this.blur();return false}j.selected=i.anchors.index(this);i.abort();if(j.collapsible)if(u.hasClass("ui-tabs-selected")){j.selected= +-1;j.cookie&&i._cookie(j.selected,j.cookie);i.element.queue("tabs",function(){s(r,v)}).dequeue("tabs");this.blur();return false}else if(!v.length){j.cookie&&i._cookie(j.selected,j.cookie);i.element.queue("tabs",function(){q(r,w)});i.load(i.anchors.index(this));this.blur();return false}j.cookie&&i._cookie(j.selected,j.cookie);if(w.length){v.length&&i.element.queue("tabs",function(){s(r,v)});i.element.queue("tabs",function(){q(r,w)});i.load(i.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier."; +b.browser.msie&&this.blur()});this.anchors.bind("click.tabs",function(){return false})},_getIndex:function(c){if(typeof c=="string")c=this.anchors.index(this.anchors.filter("[href$="+c+"]"));return c},destroy:function(){var c=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var h= +b.data(this,"href.tabs");if(h)this.href=h;var i=b(this).unbind(".tabs");b.each(["href","load","cache"],function(j,n){i.removeData(n+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){b.data(this,"destroy.tabs")?b(this).remove():b(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});c.cookie&&this._cookie(null,c.cookie);return this},add:function(c, +h,i){if(i===d)i=this.anchors.length;var j=this,n=this.options;h=b(n.tabTemplate.replace(/#\{href\}/g,c).replace(/#\{label\}/g,h));c=!c.indexOf("#")?c.replace("#",""):this._tabId(b("a",h)[0]);h.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var p=j.element.find("#"+c);p.length||(p=b(n.panelTemplate).attr("id",c).data("destroy.tabs",true));p.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(i>=this.lis.length){h.appendTo(this.list);p.appendTo(this.list[0].parentNode)}else{h.insertBefore(this.lis[i]); +p.insertBefore(this.panels[i])}n.disabled=b.map(n.disabled,function(l){return l>=i?++l:l});this._tabify();if(this.anchors.length==1){n.selected=0;h.addClass("ui-tabs-selected ui-state-active");p.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){j._trigger("show",null,j._ui(j.anchors[0],j.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[i],this.panels[i]));return this},remove:function(c){c=this._getIndex(c);var h=this.options,i=this.lis.eq(c).remove(),j=this.panels.eq(c).remove(); +if(i.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(c+(c+1=c?--n:n});this._tabify();this._trigger("remove",null,this._ui(i.find("a")[0],j[0]));return this},enable:function(c){c=this._getIndex(c);var h=this.options;if(b.inArray(c,h.disabled)!=-1){this.lis.eq(c).removeClass("ui-state-disabled");h.disabled=b.grep(h.disabled,function(i){return i!=c});this._trigger("enable",null, +this._ui(this.anchors[c],this.panels[c]));return this}},disable:function(c){c=this._getIndex(c);var h=this.options;if(c!=h.selected){this.lis.eq(c).addClass("ui-state-disabled");h.disabled.push(c);h.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[c],this.panels[c]))}return this},select:function(c){c=this._getIndex(c);if(c==-1)if(this.options.collapsible&&this.options.selected!=-1)c=this.options.selected;else return this;this.anchors.eq(c).trigger(this.options.event+".tabs");return this}, +load:function(c){c=this._getIndex(c);var h=this,i=this.options,j=this.anchors.eq(c)[0],n=b.data(j,"load.tabs");this.abort();if(!n||this.element.queue("tabs").length!==0&&b.data(j,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(c).addClass("ui-state-processing");if(i.spinner){var p=b("span",j);p.data("label.tabs",p.html()).html(i.spinner)}this.xhr=b.ajax(b.extend({},i.ajaxOptions,{url:n,success:function(l,k){h.element.find(h._sanitizeSelector(j.hash)).html(l);h._cleanup();i.cache&&b.data(j, +"cache.tabs",true);h._trigger("load",null,h._ui(h.anchors[c],h.panels[c]));try{i.ajaxOptions.success(l,k)}catch(m){}},error:function(l,k){h._cleanup();h._trigger("load",null,h._ui(h.anchors[c],h.panels[c]));try{i.ajaxOptions.error(l,k,c,j)}catch(m){}}}));h.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this}, +url:function(c,h){this.anchors.eq(c).removeData("cache.tabs").data("load.tabs",h);return this},length:function(){return this.anchors.length}});b.extend(b.ui.tabs,{version:"1.8.11"});b.extend(b.ui.tabs.prototype,{rotation:null,rotate:function(c,h){var i=this,j=this.options,n=i._rotate||(i._rotate=function(p){clearTimeout(i.rotation);i.rotation=setTimeout(function(){var l=j.selected;i.select(++l)[^>]*$|#([\w\-]+)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Check for digits + rdigit = /\d/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The deferred used on DOM ready + readyList, + + // Promise methods + promiseMethods = "then done fail isResolved isRejected promise".split( " " ), + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = "body"; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.5.1", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.done( fn ); + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + // A third-party is pushing the ready event forwards + if ( wait === true ) { + jQuery.readyWait--; + } + + // Make sure that the DOM is not already loaded + if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyBound ) { + return; + } + + readyBound = true; + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNaN: function( obj ) { + return obj == null || !rdigit.test( obj ) || isNaN( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test(data.replace(rvalidescape, "@") + .replace(rvalidtokens, "]") + .replace(rvalidbraces, "")) ) { + + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); + + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, + + // Cross-browser xml parsing + // (xml & tmp used internally) + parseXML: function( data , xml , tmp ) { + + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + + tmp = xml.documentElement; + + if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { + jQuery.error( "Invalid XML: " + data ); + } + + return xml; + }, + + noop: function() {}, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && rnotwhite.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement, + script = document.createElement( "script" ); + + if ( jQuery.support.scriptEval() ) { + script.appendChild( document.createTextNode( data ) ); + } else { + script.text = data; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type(array); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } + + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } + + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } + + // So proxy can be declared as an argument + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can be optionally by executed if its a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return (new Date()).getTime(); + }, + + // Create a simple deferred (one callbacks list) + _Deferred: function() { + var // callbacks list + callbacks = [], + // stored [ context , args ] + fired, + // to avoid firing when already doing so + firing, + // flag to know if the deferred has been cancelled + cancelled, + // the deferred itself + deferred = { + + // done( f1, f2, ...) + done: function() { + if ( !cancelled ) { + var args = arguments, + i, + length, + elem, + type, + _fired; + if ( fired ) { + _fired = fired; + fired = 0; + } + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + deferred.done.apply( deferred, elem ); + } else if ( type === "function" ) { + callbacks.push( elem ); + } + } + if ( _fired ) { + deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); + } + } + return this; + }, + + // resolve with given context and args + resolveWith: function( context, args ) { + if ( !cancelled && !fired && !firing ) { + firing = 1; + try { + while( callbacks[ 0 ] ) { + callbacks.shift().apply( context, args ); + } + } + // We have to add a catch block for + // IE prior to 8 or else the finally + // block will never get executed + catch (e) { + throw e; + } + finally { + fired = [ context, args ]; + firing = 0; + } + } + return this; + }, + + // resolve with this as context and given arguments + resolve: function() { + deferred.resolveWith( jQuery.isFunction( this.promise ) ? this.promise() : this, arguments ); + return this; + }, + + // Has this deferred been resolved? + isResolved: function() { + return !!( firing || fired ); + }, + + // Cancel + cancel: function() { + cancelled = 1; + callbacks = []; + return this; + } + }; + + return deferred; + }, + + // Full fledged deferred (two callbacks list) + Deferred: function( func ) { + var deferred = jQuery._Deferred(), + failDeferred = jQuery._Deferred(), + promise; + // Add errorDeferred methods, then and promise + jQuery.extend( deferred, { + then: function( doneCallbacks, failCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ); + return this; + }, + fail: failDeferred.done, + rejectWith: failDeferred.resolveWith, + reject: failDeferred.resolve, + isRejected: failDeferred.isResolved, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + if ( promise ) { + return promise; + } + promise = obj = {}; + } + var i = promiseMethods.length; + while( i-- ) { + obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ]; + } + return obj; + } + } ); + // Make sure only one callback list will be used + deferred.done( failDeferred.cancel ).fail( deferred.cancel ); + // Unexpose cancel + delete deferred.cancel; + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + return deferred; + }, + + // Deferred helper + when: function( object ) { + var lastIndex = arguments.length, + deferred = lastIndex <= 1 && object && jQuery.isFunction( object.promise ) ? + object : + jQuery.Deferred(), + promise = deferred.promise(); + + if ( lastIndex > 1 ) { + var array = slice.call( arguments, 0 ), + count = lastIndex, + iCallback = function( index ) { + return function( value ) { + array[ index ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value; + if ( !( --count ) ) { + deferred.resolveWith( promise, array ); + } + }; + }; + while( ( lastIndex-- ) ) { + object = array[ lastIndex ]; + if ( object && jQuery.isFunction( object.promise ) ) { + object.promise().then( iCallback(lastIndex), deferred.reject ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( promise, array ); + } + } else if ( deferred !== object ) { + deferred.resolve( object ); + } + return promise; + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySubclass( selector, context ) { + return new jQuerySubclass.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySubclass, this ); + jQuerySubclass.superclass = this; + jQuerySubclass.fn = jQuerySubclass.prototype = this(); + jQuerySubclass.fn.constructor = jQuerySubclass; + jQuerySubclass.subclass = this.subclass; + jQuerySubclass.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) { + context = jQuerySubclass(context); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass ); + }; + jQuerySubclass.fn.init.prototype = jQuerySubclass.fn; + var rootjQuerySubclass = jQuerySubclass(document); + return jQuerySubclass; + }, + + browser: {} +}); + +// Create readyList deferred +readyList = jQuery._Deferred(); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +if ( indexOf ) { + jQuery.inArray = function( elem, array ) { + return indexOf.call( array, elem ); + }; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +// Expose jQuery to the global object +return jQuery; + +})(); + + +(function() { + + jQuery.support = {}; + + var div = document.createElement("div"); + + div.style.display = "none"; + div.innerHTML = "
        a"; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0], + select = document.createElement("select"), + opt = select.appendChild( document.createElement("option") ), + input = div.getElementsByTagName("input")[0]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: input.value === "on", + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Will be defined later + deleteExpando: true, + optDisabled: false, + checkClone: false, + noCloneEvent: true, + noCloneChecked: true, + boxModel: null, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableHiddenOffsets: true + }; + + input.checked = true; + jQuery.support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as diabled) + select.disabled = true; + jQuery.support.optDisabled = !opt.disabled; + + var _scriptEval = null; + jQuery.support.scriptEval = function() { + if ( _scriptEval === null ) { + var root = document.documentElement, + script = document.createElement("script"), + id = "script" + jQuery.now(); + + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + _scriptEval = true; + delete window[ id ]; + } else { + _scriptEval = false; + } + + root.removeChild( script ); + // release memory in IE + root = script = id = null; + } + + return _scriptEval; + }; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + + } catch(e) { + jQuery.support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + div = document.createElement("div"); + div.innerHTML = ""; + + var fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function() { + var div = document.createElement("div"), + body = document.getElementsByTagName("body")[0]; + + // Frameset documents with no body should not run this code + if ( !body ) { + return; + } + + div.style.width = div.style.paddingLeft = "1px"; + body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + + if ( "zoom" in div.style ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2; + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "
        "; + jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; + } + + div.innerHTML = "
        t
        "; + var tds = div.getElementsByTagName("td"); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0; + + tds[0].style.display = ""; + tds[1].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE < 8 fail this test) + jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0; + div.innerHTML = ""; + + body.removeChild( div ).style.display = "none"; + div = tds = null; + }); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + var eventSupported = function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( !el.attachEvent ) { + return true; + } + + var isSupported = (eventName in el); + if ( !isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + }; + + jQuery.support.submitBubbles = eventSupported("submit"); + jQuery.support.changeBubbles = eventSupported("change"); + + // release memory in IE + div = all = a = null; +})(); + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ jQuery.expando ] = id = ++jQuery.uuid; + } else { + id = jQuery.expando; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery + // metadata on plain JS objects when the object is serialized using + // JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); + } else { + cache[ id ] = jQuery.extend(cache[ id ], name); + } + } + + thisCache = cache[ id ]; + + // Internal jQuery data is stored in a separate object inside the object's data + // cache in order to avoid key collisions between internal data and user-defined + // data + if ( pvt ) { + if ( !thisCache[ internalKey ] ) { + thisCache[ internalKey ] = {}; + } + + thisCache = thisCache[ internalKey ]; + } + + if ( data !== undefined ) { + thisCache[ name ] = data; + } + + // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should + // not attempt to inspect the internal events object using jQuery.data, as this + // internal data object is undocumented and subject to change. + if ( name === "events" && !thisCache[name] ) { + return thisCache[ internalKey ] && thisCache[ internalKey ].events; + } + + return getByName ? thisCache[ name ] : thisCache; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var internalKey = jQuery.expando, isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; + + if ( thisCache ) { + delete thisCache[ name ]; + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !isEmptyDataObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( pvt ) { + delete cache[ id ][ internalKey ]; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + var internalCache = cache[ id ][ internalKey ]; + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + if ( jQuery.support.deleteExpando || cache != window ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the entire user cache at once because it's faster than + // iterating through each key, but we need to continue to persist internal + // data if it existed + if ( internalCache ) { + cache[ id ] = {}; + // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery + // metadata on plain JS objects when the object is serialized using + // JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + + cache[ id ][ internalKey ] = internalCache; + + // Otherwise, we need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + } else if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } else { + elem[ jQuery.expando ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + data = jQuery.data( this[0] ); + + if ( this[0].nodeType === 1 ) { + var attr = this[0].attributes, name; + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = name.substr( 5 ); + dataAttr( this[0], name, data[ name ] ); + } + } + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + data = dataAttr( this[0], key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + + } else { + return this.each(function() { + var $this = jQuery( this ), + args = [ parts[0], value ]; + + $this.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + $this.triggerHandler( "changeData" + parts[1] + "!", args ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + data = elem.getAttribute( "data-" + key ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + !jQuery.isNaN( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON +// property to be considered empty objects; this property always exists in +// order to make sure JSON.stringify does not expose internal metadata +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +jQuery.extend({ + queue: function( elem, type, data ) { + if ( !elem ) { + return; + } + + type = (type || "fx") + "queue"; + var q = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( !data ) { + return q || []; + } + + if ( !q || jQuery.isArray(data) ) { + q = jQuery._data( elem, type, jQuery.makeArray(data) ); + + } else { + q.push( data ); + } + + return q; + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(); + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue", true ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function( i ) { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspaces = /\s+/, + rreturn = /\r/g, + rspecialurl = /^(?:href|src|style)$/, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rradiocheck = /^(?:radio|checkbox)$/i; + +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name, fn ) { + return this.each(function(){ + jQuery.attr( this, name, "" ); + if ( this.nodeType === 1 ) { + this.removeAttribute( name ); + } + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspaces ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", + setClass = elem.className; + + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split( rspaces ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspaces ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + if ( !arguments.length ) { + var elem = this[0]; + + if ( elem ) { + if ( jQuery.nodeName( elem, "option" ) ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + } + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { + return elem.getAttribute("value") === null ? "on" : elem.value; + } + + // Everything else, we just grab the value + return (elem.value || "").replace(rreturn, ""); + + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var self = jQuery(this), val = value; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call(this, i, self.val()); + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray(val) ) { + val = jQuery.map(val, function (value) { + return value == null ? "" : value + ""; + }); + } + + if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { + this.checked = jQuery.inArray( self.val(), val ) >= 0; + + } else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(val); + + jQuery( "option", this ).each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + this.selectedIndex = -1; + } + + } else { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery(elem)[name](value); + } + + var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // Only do all the following if this is a node (faster for style) + if ( elem.nodeType === 1 ) { + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + + // If applicable, access the attribute via the DOM 0 way + // 'in' checks fail in Blackberry 4.7 #6931 + if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + if ( value === null ) { + if ( elem.nodeType === 1 ) { + elem.removeAttribute( name ); + } + + } else { + elem[ name ] = value; + } + } + + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; + } + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } + + return elem.style.cssText; + } + + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + // Ensure that missing attributes return undefined + // Blackberry 4.7 returns "" from getAttribute #6938 + if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { + return undefined; + } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } + // Handle everything which isn't a DOM element node + if ( set ) { + elem[ name ] = value; + } + return elem[ name ]; + } +}); + + + + +var rnamespaces = /\.(.*)$/, + rformElems = /^(?:textarea|input|select)$/i, + rperiod = /\./g, + rspace = / /g, + rescape = /[^\w\s.|`]/g, + fcleanup = function( nm ) { + return nm.replace(rescape, "\\$&"); + }; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // TODO :: Use a try/catch until it's safe to pull this out (likely 1.6) + // Minor release fix for bug #8018 + try { + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) { + elem = window; + } + } + catch ( e ) {} + + if ( handler === false ) { + handler = returnFalse; + } else if ( !handler ) { + // Fixes bug #7229. Fix recommended by jdalton + return; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery._data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData.events, + eventHandle = elemData.handle; + + if ( !events ) { + elemData.events = events = {}; + } + + if ( !eventHandle ) { + elemData.handle = eventHandle = function() { + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + if ( !handleObj.guid ) { + handleObj.guid = handler.guid; + } + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for global triggering + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( handler === false ) { + handler = returnFalse; + } + + var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + events = elemData && elemData.events; + + if ( !elemData || !events ) { + return; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem, undefined, true ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem /*, bubbling */ ) { + // Event object or event type + var type = event.type || event, + bubbling = arguments[3]; + + if ( !bubbling ) { + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + + // Only trigger if we've ever bound an event for it + if ( jQuery.event.global[ type ] ) { + // XXX This code smells terrible. event.js should not be directly + // inspecting the data cache + jQuery.each( jQuery.cache, function() { + // internalKey variable is just used to make it easier to find + // and potentially change this stuff later; currently it just + // points to jQuery.expando + var internalKey = jQuery.expando, + internalCache = this[ internalKey ]; + if ( internalCache && internalCache.events && internalCache.events[ type ] ) { + jQuery.event.trigger( event, data, internalCache.handle.elem ); + } + }); + } + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray( data ); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = jQuery._data( elem, "handle" ); + + if ( handle ) { + handle.apply( elem, data ); + } + + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script + try { + if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + event.preventDefault(); + } + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (inlineError) {} + + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); + + } else if ( !event.isDefaultPrevented() ) { + var old, + target = event.target, + targetType = type.replace( rnamespaces, "" ), + isClick = jQuery.nodeName( target, "a" ) && targetType === "click", + special = jQuery.event.special[ targetType ] || {}; + + if ( (!special._default || special._default.call( elem, event ) === false) && + !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + + try { + if ( target[ targetType ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + targetType ]; + + if ( old ) { + target[ "on" + targetType ] = null; + } + + jQuery.event.triggered = true; + target[ targetType ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (triggerError) {} + + if ( old ) { + target[ "on" + targetType ] = old; + } + + jQuery.event.triggered = false; + } + } + }, + + handle: function( event ) { + var all, handlers, namespaces, namespace_re, events, + namespace_sort = [], + args = jQuery.makeArray( arguments ); + + event = args[0] = jQuery.event.fix( event || window.event ); + event.currentTarget = this; + + // Namespaced event handlers + all = event.type.indexOf(".") < 0 && !event.exclusive; + + if ( !all ) { + namespaces = event.type.split("."); + event.type = namespaces.shift(); + namespace_sort = namespaces.slice(0).sort(); + namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.namespace = event.namespace || namespace_sort.join("."); + + events = jQuery._data(this, "events"); + + handlers = (events || {})[ event.type ]; + + if ( events && handlers ) { + // Clone the handlers to prevent manipulation + handlers = handlers.slice(0); + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + } + + return event.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 pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + // Fixes #1925 where srcElement might not be defined either + event.target = event.srcElement || document; + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, + body = document.body; + + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { + event.which = event.charCode != null ? event.charCode : event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, + liveConvert( handleObj.origType, handleObj.selector ), + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); + }, + + remove: function( handleObj ) { + jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); + } + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + + // Chrome does something similar, the parentNode property + // can be accessed but is null. + if ( parent !== document && !parent.parentNode ) { + return; + } + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( this.nodeName && this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery._data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery._data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + e.liveFired = undefined; + jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + beforedeactivate: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information + beforeactivate: function( e ) { + var elem = e.target; + jQuery._data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return rformElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return rformElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; + + // Handle when the input is .focus()'d + changeFilters.focus = changeFilters.beforeactivate; +} + +function trigger( type, elem, args ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + // Don't pass args or remember liveFired; they apply to the donor event. + var event = jQuery.extend( {}, args[ 0 ] ); + event.type = type; + event.originalEvent = {}; + event.liveFired = undefined; + jQuery.event.handle.call( elem, event ); + if ( event.isDefaultPrevented() ) { + args[ 0 ].preventDefault(); + } +} + +// Create "bubbling" focus and blur events +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + this.addEventListener( orig, handler, true ); + }, + teardown: function() { + this.removeEventListener( orig, handler, true ); + } + }; + + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.handle.call( this, e ); + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) || data === false ) { + fn = data; + data = undefined; + } + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }) : fn; + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + var event = jQuery.Event( type ); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + i = 1; + + // link all the functions, so any of them can unbind this click handler + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); + } + + return this.click( jQuery.proxy( fn, function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + })); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( typeof types === "object" && !types.preventDefault ) { + for ( var key in types ) { + context[ name ]( key, data, types[key], selector ); + } + + return this; + } + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( type === "focus" || type === "blur" ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + for ( var j = 0, l = context.length; j < l; j++ ) { + jQuery.event.add( context[j], "live." + liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + } + + } else { + // unbind live handler + context.unbind( "live." + liveConvert( type, selector ), fn ); + } + } + + return this; + }; +}); + +function liveHandler( event ) { + var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + elems = [], + selectors = [], + events = jQuery._data( this, "events" ); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) + if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { + return; + } + + if ( event.namespace ) { + namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + close = match[i]; + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) { + elem = close.elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + event.type = handleObj.preType; + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj, level: close.level }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + + if ( maxLevel && match.level > maxLevel ) { + break; + } + + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + ret = match.handleObj.origHandler.apply( match.elem, arguments ); + + if ( ret === false || event.isPropagationStopped() ) { + maxLevel = match.level; + + if ( ret === false ) { + stop = false; + } + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&"); +} + +jQuery.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( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.bind( name, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var match, + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var found, item, + filter = Expr.filter[ type ], + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.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|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return "text" === elem.getAttribute( 'type' ); + }, + radio: function( elem ) { + return "radio" === elem.type; + }, + + checkbox: function( elem ) { + return "checkbox" === elem.type; + }, + + file: function( elem ) { + return "file" === elem.type; + }, + password: function( elem ) { + return "password" === elem.type; + }, + + submit: function( elem ) { + return "submit" === elem.type; + }, + + image: function( elem ) { + return "image" === elem.type; + }, + + reset: function( elem ) { + return "reset" === elem.type; + }, + + button: function( elem ) { + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + var first = match[2], + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // If the nodes are siblings (or identical) we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "

        "; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector, + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + if ( matches ) { + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + return matches.call( node, expr ); + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
        "; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var ret = this.pushStack( "", "find", selector ), + length = 0; + + for ( var i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( var n = length; n < ret.length; n++ ) { + for ( var r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && jQuery.filter( selector, this ).length > 0; + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + if ( jQuery.isArray( selectors ) ) { + var match, selector, + matches = {}, + level = 1; + + if ( cur && selectors.length ) { + for ( i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[selector] ) { + matches[selector] = jQuery.expr.match.POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[selector]; + + if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + ret.push({ selector: selector, elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + } + + return ret; + } + + var pos = POS.test( selectors ) ? + jQuery( selectors, context || this.context ) : null; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique(ret) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ), + // The variable 'args' was introduced in + // https://github.com/jquery/jquery/commit/52a0238 + // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. + // http://code.google.com/p/v8/issues/detail?id=1050 + args = slice.call(arguments); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, args.join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +} + + + + +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /", "" ], + legend: [ 1, "
        ", "
        " ], + thead: [ 1, "", "
        " ], + tr: [ 2, "", "
        " ], + td: [ 3, "", "
        " ], + col: [ 2, "", "
        " ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and + + + + + + + From 4e8b8a28f649755a1b1a278c23c851c96875bbf9 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Wed, 27 Feb 2013 13:01:19 -0800 Subject: [PATCH 0442/1398] Now extracting the region from the QueueUrl param when signing SQS requests. Fixes #179 --- features/sqs/queues.feature | 4 ++++ features/sqs/step_definitions/messages.rb | 6 ++++++ features/sqs/step_definitions/queues.rb | 5 +++++ lib/aws/record/abstract_base.rb | 2 +- lib/aws/sqs/request.rb | 13 +++++++++++++ 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/features/sqs/queues.feature b/features/sqs/queues.feature index fb85b37c269..2bea10c690b 100644 --- a/features/sqs/queues.feature +++ b/features/sqs/queues.feature @@ -133,3 +133,7 @@ Feature: SQS Queues Then a request should have been made like: | TYPE | NAME | VALUE | | param | Action | DeleteMessageBatch | + + Scenario: Making a queue request across regions + When I create a queue in "us-west-1" + Then I should be able to send a message using a "us-east-1" client diff --git a/features/sqs/step_definitions/messages.rb b/features/sqs/step_definitions/messages.rb index 23708e7d090..53839460a78 100644 --- a/features/sqs/step_definitions/messages.rb +++ b/features/sqs/step_definitions/messages.rb @@ -167,3 +167,9 @@ Then /^the message should have a string sender ID$/ do @message.sender_id.should be_a(String) end + +Then /^I should be able to send a message using a "(.*?)" client$/ do |region| + @sqs = AWS::SQS.new(:sqs_endpoint => "sqs.#{region}.amazonaws.com") + @sqs.queues[@queue.url].send_message('HELLO') +end + diff --git a/features/sqs/step_definitions/queues.rb b/features/sqs/step_definitions/queues.rb index d2dbd33b2af..5376f488a5b 100644 --- a/features/sqs/step_definitions/queues.rb +++ b/features/sqs/step_definitions/queues.rb @@ -17,6 +17,11 @@ @created_queues << @queue end +Given /^I create a queue in "(.*?)"$/ do |region| + @sqs = AWS::SQS.new(:sqs_endpoint => "sqs.#{region}.amazonaws.com") + step "I create a queue" +end + Then /^the result should be a queue object$/ do @result.should be_an(SQS::Queue) end diff --git a/lib/aws/record/abstract_base.rb b/lib/aws/record/abstract_base.rb index 27813911dbc..a5d375265f4 100644 --- a/lib/aws/record/abstract_base.rb +++ b/lib/aws/record/abstract_base.rb @@ -298,7 +298,7 @@ def update protected def populate_id - @_id = UUIDTools::UUID.random_create.to_s + @_id = UUIDTools::UUID.random_create.to_s.downcase end protected diff --git a/lib/aws/sqs/request.rb b/lib/aws/sqs/request.rb index b5ecd48b0ff..5f0573d4635 100644 --- a/lib/aws/sqs/request.rb +++ b/lib/aws/sqs/request.rb @@ -11,6 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +require 'uri' + module AWS class SQS @@ -35,6 +37,17 @@ def uri path end + def region + # sigv4 requires the region name when signing, this should come from + # the QueueUrl param whenever present + if param = params.find{|p| p.name == 'QueueUrl' } + if matches = URI.parse(param.value).host.match(/^sqs\.(.+?)\./) + return matches[1] + end + end + super + end + private def full_url From 51724a14d5f593091fdf3720848552599f1da9a1 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Wed, 27 Feb 2013 13:21:10 -0800 Subject: [PATCH 0443/1398] Updated SQS region logic to fail gracefully when the QueueUrl is not valid. --- lib/aws/sqs/request.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/aws/sqs/request.rb b/lib/aws/sqs/request.rb index 5f0573d4635..6e6883e6e7d 100644 --- a/lib/aws/sqs/request.rb +++ b/lib/aws/sqs/request.rb @@ -40,12 +40,15 @@ def uri def region # sigv4 requires the region name when signing, this should come from # the QueueUrl param whenever present - if param = params.find{|p| p.name == 'QueueUrl' } - if matches = URI.parse(param.value).host.match(/^sqs\.(.+?)\./) - return matches[1] - end + if + param = params.find{|p| p.name == 'QueueUrl' } and + host = URI.parse(param.value).host and + matches = host.match(/^sqs\.(.+?)\./) + then + return matches[1] + else + super end - super end private From 781681b1e0d22dcea37fe32361af6842ed094910 Mon Sep 17 00:00:00 2001 From: Ishiro Date: Thu, 28 Feb 2013 00:19:21 +0100 Subject: [PATCH 0444/1398] Add :min_adjustment_step option for AutoScaling policies --- lib/aws/auto_scaling/scaling_policy.rb | 4 ++-- lib/aws/auto_scaling/scaling_policy_options.rb | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/aws/auto_scaling/scaling_policy.rb b/lib/aws/auto_scaling/scaling_policy.rb index c90c951465d..426587b55f7 100644 --- a/lib/aws/auto_scaling/scaling_policy.rb +++ b/lib/aws/auto_scaling/scaling_policy.rb @@ -26,7 +26,7 @@ class AutoScaling # # @attr_reader [Integer] cooldown # - # @attr_reader [Integer] min_adjustment_magnitude + # @attr_reader [Integer] min_adjustment_step # class ScalingPolicy < Core::Resource @@ -63,7 +63,7 @@ def initialize auto_scaling_group, policy_name, options = {} attribute :cooldown - attribute :min_adjustment_magintude + attribute :min_adjustment_step populates_from(:describe_policies) do |resp| resp.scaling_policies.find do |p| diff --git a/lib/aws/auto_scaling/scaling_policy_options.rb b/lib/aws/auto_scaling/scaling_policy_options.rb index fc8b884b9a3..e5110f2e029 100644 --- a/lib/aws/auto_scaling/scaling_policy_options.rb +++ b/lib/aws/auto_scaling/scaling_policy_options.rb @@ -40,7 +40,7 @@ module ScalingPolicyOptions # after a scaling activity completes before any further # trigger-related scaling activities can start. # - # @option options [Integer] :min_adjustment_magnitude + # @option options [Integer] :min_adjustment_step # # @return [Hash] # @@ -52,6 +52,7 @@ def scaling_policy_options auto_scaling_group, policy_name, options :cooldown, :adjustment_type, :scaling_adjustment, + :min_adjustment_step, ].each do |opt| opts[opt] = options[opt] if options.key?(opt) end From 8777182951d0f5c9a7f79ea4554beb07fb158a50 Mon Sep 17 00:00:00 2001 From: Ishiro Date: Thu, 28 Feb 2013 22:20:40 +0100 Subject: [PATCH 0445/1398] Tests for the :min_adjustment_step option --- .../aws/auto_scaling/scaling_policy_colletion_spec.rb | 2 ++ spec/aws/auto_scaling/scaling_policy_spec.rb | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/spec/aws/auto_scaling/scaling_policy_colletion_spec.rb b/spec/aws/auto_scaling/scaling_policy_colletion_spec.rb index cc640464e4a..5cf2d300f38 100644 --- a/spec/aws/auto_scaling/scaling_policy_colletion_spec.rb +++ b/spec/aws/auto_scaling/scaling_policy_colletion_spec.rb @@ -54,11 +54,13 @@ class AutoScaling :policy_name => 'name', :adjustment_type => 'type', :scaling_adjustment => 1, + :min_adjustment_step => 2, :cooldown => 10) policies.create('name', :adjustment_type => 'type', :scaling_adjustment => 1, + :min_adjustment_step => 2, :cooldown => 10) end diff --git a/spec/aws/auto_scaling/scaling_policy_spec.rb b/spec/aws/auto_scaling/scaling_policy_spec.rb index d610767567a..608c1dc78b1 100644 --- a/spec/aws/auto_scaling/scaling_policy_spec.rb +++ b/spec/aws/auto_scaling/scaling_policy_spec.rb @@ -35,6 +35,7 @@ class AutoScaling :policy_arn => 'arn', :adjustment_type => 'type', :scaling_adjustment => 1, + :min_adjustment_step => 2, :alarms => [ { :alarm_name => 'name1', :alarm_arn => 'arn1' }, { :alarm_name => 'name2', :alarm_arn => 'arn2' }, @@ -77,6 +78,14 @@ class AutoScaling end + context '#min_adjustment_step' do + + it 'returns the min adjustment step' do + policy.min_adjustment_step.should == 2 + end + + end + context '#cooldown' do it 'returns the described cooldown' do @@ -106,11 +115,13 @@ class AutoScaling :policy_name => policy.name, :adjustment_type => 'type2', :scaling_adjustment => 2, + :min_adjustment_step => 4, :cooldown => 3, }).and_return(resp) policy.update( :adjustment_type => 'type2', :scaling_adjustment => 2, + :min_adjustment_step => 4, :cooldown => 3) end From dcbbf363c3387cd433fd09ef566beb96fd69a695 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Thu, 28 Feb 2013 14:02:29 -0800 Subject: [PATCH 0446/1398] Fixed broken auto scaling feature. --- features/auto_scaling/step_definitions/groups.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/features/auto_scaling/step_definitions/groups.rb b/features/auto_scaling/step_definitions/groups.rb index d32456c5ed5..ddf14158b11 100644 --- a/features/auto_scaling/step_definitions/groups.rb +++ b/features/auto_scaling/step_definitions/groups.rb @@ -71,12 +71,13 @@ if key == :availability_zones got.sort_by(&:name).should == expected.sort_by(&:name) elsif key == :tags - got.should == expected.collect{|tag| + got.should eq(expected.collect{|tag| tag[:resource_type] = 'auto-scaling-group' tag[:resource_id] = @group_name tag[:propagate_at_launch] == true unless tag.key?(:propagate_at_launch) + tag[:value] = nil unless tag[:value] tag - } + }) got.each{|tag| tag.resource.should == @auto_scaling_group } else got.should == expected From cdad8279ffb9ec7f5e7677c1ad9a30ff34192db6 Mon Sep 17 00:00:00 2001 From: Carl Parker Date: Thu, 28 Feb 2013 14:53:59 -0800 Subject: [PATCH 0447/1398] fix endpoint in docs for elastictranscoder --- lib/aws/core.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aws/core.rb b/lib/aws/core.rb index c6cdcbb662b..8fb9ecfbc75 100644 --- a/lib/aws/core.rb +++ b/lib/aws/core.rb @@ -256,7 +256,7 @@ class << self # @option options [String] :elastic_beanstalk_endpoint ('elasticbeanstalk.us-east-1.amazonaws.com') # The service endpoint for AWS Elastic Beanstalk. # - # @option options [String] :elastic_transcoder_endpoint ('elastictranscoderbeanstalk.us-east-1.amazonaws.com') + # @option options [String] :elastic_transcoder_endpoint ('elastictranscoder.us-east-1.amazonaws.com') # The service endpoint for Elastic Transcoder. # # @option options [String] :elb_endpoint ('elasticloadbalancing.us-east-1.amazonaws.com') From d5827eeff03afba37786319f9833adf02f429803 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Sat, 2 Mar 2013 12:50:34 -0800 Subject: [PATCH 0448/1398] Removed old and no longer valid documentation about using session tokens with DynamoDB. --- lib/aws/dynamo_db.rb | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/aws/dynamo_db.rb b/lib/aws/dynamo_db.rb index ea9a8fb53e3..f3363a8cf48 100644 --- a/lib/aws/dynamo_db.rb +++ b/lib/aws/dynamo_db.rb @@ -20,17 +20,7 @@ module AWS # # dynamo_db = AWS::DynamoDB.new( # :access_key_id => '...', - # :secret_access_key => '...', - # :session_token => '...') - # - # = Credentials - # - # Amazon DynamoDB requires that all requests are made with short-term - # credentials (e.g. requires a session token). - # - # @note If you make a request using AWS::DynamoDB with long-term credentials - # a request is made to Amazon STS for temporary session credentials. - # These will be cached in the process and re-used. + # :secret_access_key => '...') # # = Tables # From 4c06c5a7962b655e36e477a534a0148e1c429305 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Mon, 4 Mar 2013 15:45:46 -0800 Subject: [PATCH 0449/1398] Updated bundled ca-bundle.crt file. --- ca-bundle.crt | 568 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 566 insertions(+), 2 deletions(-) diff --git a/ca-bundle.crt b/ca-bundle.crt index 1fccb3ddc82..99b310bce91 100644 --- a/ca-bundle.crt +++ b/ca-bundle.crt @@ -1,7 +1,7 @@ ## ## ca-bundle.crt -- Bundle of CA Root Certificates ## -## Certificate data from Mozilla as of: Wed Apr 25 15:02:13 2012 +## Certificate data from Mozilla as of: Sat Dec 29 20:03:40 2012 ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates @@ -14,7 +14,7 @@ ## Just configure this file as the SSLCACertificateFile. ## -# @(#) $RCSfile: certdata.txt,v $ $Revision: 1.83 $ $Date: 2012/04/25 14:49:29 $ +# @(#) $RCSfile: certdata.txt,v $ $Revision: 1.87 $ $Date: 2012/12/29 16:32:45 $ GTE CyberTrust Global Root ========================== @@ -147,6 +147,44 @@ WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf Tqj/ZA1k -----END CERTIFICATE----- +Verisign Class 1 Public Primary Certification Authority - G2 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEEzH6qqYPnHTkxD4PTqJkZIwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq0Lq+Fi24g9TK0g+8djHKlNgd +k4xWArzZbxpvUjZudVYKVdPfQ4chEWWKfo+9Id5rMj8bhDSVBZ1BNeuS65bdqlk/AVNtmU/t5eIq +WpDBucSmFc/IReumXY6cPvBkJHalzasab7bYe1FhbqZ/h8jit+U03EGI6glAvnOSPWvndQIDAQAB +MA0GCSqGSIb3DQEBBQUAA4GBAKlPww3HZ74sy9mozS11534Vnjty637rXC0Jh9ZrbWB85a7FkCMM +XErQr7Fd88e2CtvgFZMN3QO8x3aKtd1Pw5sTdbgBwObJW2uluIncrKTdcu1OofdPvAbT6shkdHvC +lUGcZXNY8ZCaPGqxmMnEh7zPRW1F4m4iP/68DzFc6PLZ +-----END CERTIFICATE----- + +Verisign Class 2 Public Primary Certification Authority - G2 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGljIFByaW1h +cnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNp +Z24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1 +c3QgTmV0d29yazAeFw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGljIFByaW1h +cnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNp +Z24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1 +c3QgTmV0d29yazCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjx +nNuX6Zr8wgQGE75fUsjMHiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRC +wiNPStjwDqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cCAwEA +ATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9jinb3/7aHmZuovCfTK +1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAXrXfMSTWqz9iP0b63GJZHc2pUIjRk +LbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnInjBJ7xUS0rg== +-----END CERTIFICATE----- + Verisign Class 3 Public Primary Certification Authority - G2 ============================================================ -----BEGIN CERTIFICATE----- @@ -266,6 +304,54 @@ V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r on+jjBXu -----END CERTIFICATE----- +Verisign Class 1 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAN2E1Lm0+afY8wR4nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/E +bRrsC+MO8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjVojYJ +rKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjbPG7PoBMAGrgnoeS+ +Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP26KbqxzcSXKMpHgLZ2x87tNcPVkeB +FQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vrn5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +q2aN17O6x5q25lXQBfGfMY1aqtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/N +y9Sn2WCVhDr4wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3 +ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrspSCAaWihT37h +a88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4E1Z5T21Q6huwtVexN2ZYI/Pc +D98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g== +-----END CERTIFICATE----- + +Verisign Class 2 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29y +azE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ug +b25seTFFMEMGA1UEAxM8VmVyaVNpZ24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1 +c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y +aXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEArwoNwtUs22e5LeWUJ92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6 +tW8UvxDOJxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUYwZF7 +C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9okoqQHgiBVrKtaaNS +0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjNqWm6o+sdDZykIKbBoMXRRkwXbdKs +Zj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/ESrg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0 +JhU8wI1NQ0kdvekhktdmnLfexbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf +0xwLRtxyID+u7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU +sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RIsH/7NiXaldDx +JBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTPcjnhsUPgKM+351psE2tJs//j +GHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q +-----END CERTIFICATE----- + Verisign Class 3 Public Primary Certification Authority - G3 ============================================================ -----BEGIN CERTIFICATE----- @@ -686,6 +772,31 @@ gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS -----END CERTIFICATE----- +UTN-USER First-Network Applications +=================================== +-----BEGIN CERTIFICATE----- +MIIEZDCCA0ygAwIBAgIQRL4Mi1AAJLQR0zYwS8AzdzANBgkqhkiG9w0BAQUFADCBozELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xKzAp +BgNVBAMTIlVUTi1VU0VSRmlyc3QtTmV0d29yayBBcHBsaWNhdGlvbnMwHhcNOTkwNzA5MTg0ODM5 +WhcNMTkwNzA5MTg1NzQ5WjCBozELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5T +YWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xKzApBgNVBAMTIlVUTi1VU0VSRmlyc3QtTmV0d29yayBB +cHBsaWNhdGlvbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCz+5Gh5DZVhawGNFug +mliy+LUPBXeDrjKxdpJo7CNKyXY/45y2N3kDuatpjQclthln5LAbGHNhSuh+zdMvZOOmfAz6F4Cj +DUeJT1FxL+78P/m4FoCHiZMlIJpDgmkkdihZNaEdwH+DBmQWICzTSaSFtMBhf1EI+GgVkYDLpdXu +Ozr0hAReYFmnjDRy7rh4xdE7EkpvfmUnuaRVxblvQ6TFHSyZwFKkeEwVs0CYCGtDxgGwenv1axwi +P8vv/6jQOkt2FZ7S0cYu49tXGzKiuG/ohqY/cKvlcJKrRB5AUPuco2LkbG6gyN7igEL66S/ozjIE +j3yNtxyjNTwV3Z7DrpelAgMBAAGjgZEwgY4wCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFPqGydvguul49Uuo1hXf8NPhahQ8ME8GA1UdHwRIMEYwRKBCoECGPmh0dHA6Ly9j +cmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LU5ldHdvcmtBcHBsaWNhdGlvbnMuY3JsMA0G +CSqGSIb3DQEBBQUAA4IBAQCk8yXM0dSRgyLQzDKrm5ZONJFUICU0YV8qAhXhi6r/fWRRzwr/vH3Y +IWp4yy9Rb/hCHTO967V7lMPDqaAt39EpHx3+jz+7qEUqf9FuVSTiuwL7MT++6LzsQCv4AdRWOOTK +RIK1YSAhZ2X28AvnNPilwpyjXEAfhZOVBt5P1CeptqX8Fs1zMT+4ZSfP1FMa8Kxun08FDAOBp4Qp +xFq9ZFdyrTvPNximmMatBrTcCKME1SmklpoSZ0qMYEWd8SOasACcaLWYUNPvji6SZbFIPiG+FTAq +DbUMo2s/rn9X9R+WfN9v3YIwLGUbQErNaLly7HF27FSOH4UMAWr6pjisH8SE +-----END CERTIFICATE----- + America Online Root Certification Authority 1 ============================================= -----BEGIN CERTIFICATE----- @@ -973,6 +1084,26 @@ s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ FL39vmwLAw== -----END CERTIFICATE----- +Sonera Class 1 Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBJDANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG +U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MxIENBMB4XDTAxMDQwNjEwNDkxM1oXDTIxMDQw +NjEwNDkxM1owOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh +IENsYXNzMSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWJHytPZwp5/8Ue+H88 +7dF+2rDNbS82rDTG29lkFwhjMDMiikzujrsPDUJVyZ0upe/3p4zDq7mXy47vPxVnqIJyY1MPQYx9 +EJUkoVqlBvqSV536pQHydekfvFYmUk54GWVYVQNYwBSujHxVX3BbdyMGNpfzJLWaRpXk3w0LBUXl +0fIdgrvGE+D+qnr9aTCU89JFhfzyMlsy3uhsXR/LpCJ0sICOXZT3BgBLqdReLjVQCfOAl/QMF645 +2F/NM8EcyonCIvdFEu1eEpOdY6uCLrnrQkFEy0oaAIINnvmLVz5MxxftLItyM19yejhW1ebZrgUa +HXVFsculJRwSVzb9IjcCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQIR+IMi/ZT +iFIwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCLGrLJXWG04bkruVPRsoWdd44W7hE9 +28Jj2VuXZfsSZ9gqXLar5V7DtxYvyOirHYr9qxp81V9jz9yw3Xe5qObSIjiHBxTZ/75Wtf0HDjxV +yhbMp6Z3N/vbXB9OWQaHowND9Rart4S9Tu+fMTfwRvFAttEMpWT4Y14h21VOTzF2nBBhjrZTOqMR +vq9tfB69ri3iDGnHhVNoomG6xT60eVR4ngrHAr5i0RGCS2UvkVrCqIexVmiUefkl98HVrhq4uz2P +qYo4Ffdz0Fpg0YCw8NzVUM1O7pJIae2yIx4wzMiUyLb1O4Z/P6Yun/Y+LLWSlj7fLJOK/4GMDw9Z +IRlXvVWa +-----END CERTIFICATE----- + Sonera Class 2 Root CA ====================== -----BEGIN CERTIFICATE----- @@ -1092,6 +1223,32 @@ EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI -----END CERTIFICATE----- +UTN USERFirst Email Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIEojCCA4qgAwIBAgIQRL4Mi1AAJLQR0zYlJWfJiTANBgkqhkiG9w0BAQUFADCBrjELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xNjA0 +BgNVBAMTLVVUTi1VU0VSRmlyc3QtQ2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBFbWFpbDAeFw05 +OTA3MDkxNzI4NTBaFw0xOTA3MDkxNzM2NThaMIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQx +FzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsx +ITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UEAxMtVVROLVVTRVJGaXJz +dC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWlsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAsjmFpPJ9q0E7YkY3rs3BYHW8OWX5ShpHornMSMxqmNVNNRm5pELlzkniii8efNIx +B8dOtINknS4p1aJkxIW9hVE1eaROaJB7HHqkkqgX8pgV8pPMyaQylbsMTzC9mKALi+VuG6JG+ni8 +om+rWV6lL8/K2m2qL+usobNqqrcuZzWLeeEeaYji5kbNoKXqvgvOdjp6Dpvq/NonWz1zHyLmSGHG +TPNpsaguG7bUMSAsvIKKjqQOpdeJQ/wWWq8dcdcRWdq6hw2v+vPhwvCkxWeM1tZUOt4KpLoDd7Nl +yP0e03RiqhjKaJMeoYV+9Udly/hNVyh00jT/MLbu9mIwFIws6wIDAQABo4G5MIG2MAsGA1UdDwQE +AwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSJgmd9xJ0mcABLtFBIfN49rgRufTBYBgNV +HR8EUTBPME2gS6BJhkdodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLVVTRVJGaXJzdC1DbGll +bnRBdXRoZW50aWNhdGlvbmFuZEVtYWlsLmNybDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH +AwQwDQYJKoZIhvcNAQEFBQADggEBALFtYV2mGn98q0rkMPxTbyUkxsrt4jFcKw7u7mFVbwQ+zzne +xRtJlOTrIEy05p5QLnLZjfWqo7NK2lYcYJeA3IKirUq9iiv/Cwm0xtcgBEXkzYABurorbs6q15L+ +5K/r9CYdFip/bDCVNy8zEqx/3cfREYxRmLLQo5HQrfafnoOTHh1CuEava2bwm3/q4wMC5QJRwarV +NZ1yQAOJujEdxRBoUp7fooXFXAimeOZTT7Hot9MUnpOmw2TjrH5xzbyf6QMbzPvprDHBr3wVdAKZ +w7JHpsIyYdfHb0gkUSeh1YdV8nuPmD0Wnu51tvjQjvLzxq4oW6fw8zYX/MMF08oDSlQ= +-----END CERTIFICATE----- + UTN USERFirst Hardware Root CA ============================== -----BEGIN CERTIFICATE----- @@ -1118,6 +1275,31 @@ iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67 nfhmqA== -----END CERTIFICATE----- +UTN USERFirst Object Root CA +============================ +-----BEGIN CERTIFICATE----- +MIIEZjCCA06gAwIBAgIQRL4Mi1AAJLQR0zYt4LNfGzANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHTAb +BgNVBAMTFFVUTi1VU0VSRmlyc3QtT2JqZWN0MB4XDTk5MDcwOTE4MzEyMFoXDTE5MDcwOTE4NDAz +NlowgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkx +HjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3dy51c2Vy +dHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAM6qgT+jo2F4qjEAVZURnicPHxzfOpuCaDDASmEd8S8O+r5596Uj71VR +loTN2+O5bj4x2AogZ8f02b+U60cEPgLOKqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQ +w5ujm9M89RKZd7G3CeBo5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbHd2pBnqcP1/vu +lBe3/IW+pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh2JU022R5KP+6LhHC5ehbkkj7 +RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzTbafc8H9vg2XiaquHhnUCAwEAAaOBrzCBrDAL +BgNVHQ8EBAMCAcYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU2u1kdBScFDyr3ZmpvVsoTYs8 +ydgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmly +c3QtT2JqZWN0LmNybDApBgNVHSUEIjAgBggrBgEFBQcDAwYIKwYBBQUHAwgGCisGAQQBgjcKAwQw +DQYJKoZIhvcNAQEFBQADggEBAAgfUrE3RHjb/c652pWWmKpVZIC1WkDdIaXFwfNfLEzIR1pp6ujw +NTX00CXzyKakh0q9G7FzCL3Uw8q2NbtZhncxzaeAFK4T7/yxSPlrJSUtUbYsbUXBmMiKVl0+7kNO +PmsnjtA6S4ULX9Ptaqd1y9Fahy85dRNacrACgZ++8A+EVCBibGnU4U3GDZlDAQ0Slox4nb9QorFE +qmrPF3rPbw/U+CRVX/A0FklmPlBGyWNxODFiuGK581OtbLUrohKqGU8J2l7nk8aOFAj+8DCAGKCG +hU3IfdeLA/5u1fedFqySLKAj5ZyRUh+U3xeUc8OzwcFxBSAAeL0TUh2oPs0AH8g= +-----END CERTIFICATE----- + Camerfirma Chambers of Commerce Root ==================================== -----BEGIN CERTIFICATE----- @@ -1172,6 +1354,42 @@ IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== -----END CERTIFICATE----- +NetLock Qualified (Class QA) Root +================================= +-----BEGIN CERTIFICATE----- +MIIG0TCCBbmgAwIBAgIBezANBgkqhkiG9w0BAQUFADCByTELMAkGA1UEBhMCSFUxETAPBgNVBAcT +CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV +BAsTEVRhbnVzaXR2YW55a2lhZG9rMUIwQAYDVQQDEzlOZXRMb2NrIE1pbm9zaXRldHQgS296amVn +eXpvaSAoQ2xhc3MgUUEpIFRhbnVzaXR2YW55a2lhZG8xHjAcBgkqhkiG9w0BCQEWD2luZm9AbmV0 +bG9jay5odTAeFw0wMzAzMzAwMTQ3MTFaFw0yMjEyMTUwMTQ3MTFaMIHJMQswCQYDVQQGEwJIVTER +MA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNhZ2kgS2Z0 +LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxQjBABgNVBAMTOU5ldExvY2sgTWlub3NpdGV0 +dCBLb3pqZWd5em9pIChDbGFzcyBRQSkgVGFudXNpdHZhbnlraWFkbzEeMBwGCSqGSIb3DQEJARYP +aW5mb0BuZXRsb2NrLmh1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx1Ilstg91IRV +CacbvWy5FPSKAtt2/GoqeKvld/Bu4IwjZ9ulZJm53QE+b+8tmjwi8F3JV6BVQX/yQ15YglMxZc4e +8ia6AFQer7C8HORSjKAyr7c3sVNnaHRnUPYtLmTeriZ539+Zhqurf4XsoPuAzPS4DB6TRWO53Lhb +m+1bOdRfYrCnjnxmOCyqsQhjF2d9zL2z8cM/z1A57dEZgxXbhxInlrfa6uWdvLrqOU+L73Sa58XQ +0uqGURzk/mQIKAR5BevKxXEOC++r6uwSEaEYBTJp0QwsGj0lmT+1fMptsK6ZmfoIYOcZwvK9UdPM +0wKswREMgM6r3JSda6M5UzrWhQIDAMV9o4ICwDCCArwwEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV +HQ8BAf8EBAMCAQYwggJ1BglghkgBhvhCAQ0EggJmFoICYkZJR1lFTEVNISBFemVuIHRhbnVzaXR2 +YW55IGEgTmV0TG9jayBLZnQuIE1pbm9zaXRldHQgU3pvbGdhbHRhdGFzaSBTemFiYWx5emF0YWJh +biBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBBIG1pbm9zaXRldHQgZWxla3Ryb25p +a3VzIGFsYWlyYXMgam9naGF0YXMgZXJ2ZW55ZXN1bGVzZW5laywgdmFsYW1pbnQgZWxmb2dhZGFz +YW5hayBmZWx0ZXRlbGUgYSBNaW5vc2l0ZXR0IFN6b2xnYWx0YXRhc2kgU3phYmFseXphdGJhbiwg +YXogQWx0YWxhbm9zIFN6ZXJ6b2Rlc2kgRmVsdGV0ZWxla2JlbiBlbG9pcnQgZWxsZW5vcnplc2kg +ZWxqYXJhcyBtZWd0ZXRlbGUuIEEgZG9rdW1lbnR1bW9rIG1lZ3RhbGFsaGF0b2sgYSBodHRwczov +L3d3dy5uZXRsb2NrLmh1L2RvY3MvIGNpbWVuIHZhZ3kga2VyaGV0b2sgYXogaW5mb0BuZXRsb2Nr +Lm5ldCBlLW1haWwgY2ltZW4uIFdBUk5JTkchIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0 +aGlzIGNlcnRpZmljYXRlIGFyZSBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIFF1YWxpZmllZCBDUFMg +YXZhaWxhYmxlIGF0IGh0dHBzOi8vd3d3Lm5ldGxvY2suaHUvZG9jcy8gb3IgYnkgZS1tYWlsIGF0 +IGluZm9AbmV0bG9jay5uZXQwHQYDVR0OBBYEFAlqYhaSsFq7VQ7LdTI6MuWyIckoMA0GCSqGSIb3 +DQEBBQUAA4IBAQCRalCc23iBmz+LQuM7/KbD7kPgz/PigDVJRXYC4uMvBcXxKufAQTPGtpvQMznN +wNuhrWw3AkxYQTvyl5LGSKjN5Yo5iWH5Upfpvfb5lHTocQ68d4bDBsxafEp+NFAwLvt/MpqNPfMg +W/hqyobzMUwsWYACff44yTB1HLdV47yfuqhthCgFdbOLDcCRVCHnpgu0mfVRQdzNo0ci2ccBgcTc +R08m6h/t280NmPSjnLRzMkqWmf68f8glWPhY83ZmiVSkpj7EUFy6iRiCdUgh0k8T6GB+B3bbELVR +5qq5aKrN9p2QdRLqOBrKROi3macqaJVmlaut74nLYKkGEsaUR+ko +-----END CERTIFICATE----- + NetLock Notary (Class A) Root ============================= -----BEGIN CERTIFICATE----- @@ -1668,6 +1886,37 @@ hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P UrbnBEI= -----END CERTIFICATE----- +SwissSign Platinum CA - G2 +========================== +-----BEGIN CERTIFICATE----- +MIIFwTCCA6mgAwIBAgIITrIAZwwDXU8wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UEBhMCQ0gxFTAT +BgNVBAoTDFN3aXNzU2lnbiBBRzEjMCEGA1UEAxMaU3dpc3NTaWduIFBsYXRpbnVtIENBIC0gRzIw +HhcNMDYxMDI1MDgzNjAwWhcNMzYxMDI1MDgzNjAwWjBJMQswCQYDVQQGEwJDSDEVMBMGA1UEChMM +U3dpc3NTaWduIEFHMSMwIQYDVQQDExpTd2lzc1NpZ24gUGxhdGludW0gQ0EgLSBHMjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMrfogLi2vj8Bxax3mCq3pZcZB/HL37PZ/pEQtZ2Y5Wu +669yIIpFR4ZieIbWIDkm9K6j/SPnpZy1IiEZtzeTIsBQnIJ71NUERFzLtMKfkr4k2HtnIuJpX+UF +eNSH2XFwMyVTtIc7KZAoNppVRDBopIOXfw0enHb/FZ1glwCNioUD7IC+6ixuEFGSzH7VozPY1kne +WCqv9hbrS3uQMpe5up1Y8fhXSQQeol0GcN1x2/ndi5objM89o03Oy3z2u5yg+gnOI2Ky6Q0f4nIo +j5+saCB9bzuohTEJfwvH6GXp43gOCWcwizSC+13gzJ2BbWLuCB4ELE6b7P6pT1/9aXjvCR+htL/6 +8++QHkwFix7qepF6w9fl+zC8bBsQWJj3Gl/QKTIDE0ZNYWqFTFJ0LwYfexHihJfGmfNtf9dng34T +aNhxKFrYzt3oEBSa/m0jh26OWnA81Y0JAKeqvLAxN23IhBQeW71FYyBrS3SMvds6DsHPWhaPpZjy +domyExI7C3d3rLvlPClKknLKYRorXkzig3R3+jVIeoVNjZpTxN94ypeRSCtFKwH3HBqi7Ri6Cr2D ++m+8jVeTO9TUps4e8aCxzqv9KyiaTxvXw3LbpMS/XUz13XuWae5ogObnmLo2t/5u7Su9IPhlGdpV +CX4l3P5hYnL5fhgC72O00Puv5TtjjGePAgMBAAGjgawwgakwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFFCvzAeHFUdvOMW0ZdHelarp35zMMB8GA1UdIwQYMBaAFFCv +zAeHFUdvOMW0ZdHelarp35zMMEYGA1UdIAQ/MD0wOwYJYIV0AVkBAQEBMC4wLAYIKwYBBQUHAgEW +IGh0dHA6Ly9yZXBvc2l0b3J5LnN3aXNzc2lnbi5jb20vMA0GCSqGSIb3DQEBBQUAA4ICAQAIhab1 +Fgz8RBrBY+D5VUYI/HAcQiiWjrfFwUF1TglxeeVtlspLpYhg0DB0uMoI3LQwnkAHFmtllXcBrqS3 +NQuB2nEVqXQXOHtYyvkv+8Bldo1bAbl93oI9ZLi+FHSjClTTLJUYFzX1UWs/j6KWYTl4a0vlpqD4 +U99REJNi54Av4tHgvI42Rncz7Lj7jposiU0xEQ8mngS7twSNC/K5/FqdOxa3L8iYq/6KUFkuozv8 +KV2LwUvJ4ooTHbG/u0IdUt1O2BReEMYxB+9xJ/cbOQncguqLs5WGXv312l0xpuAxtpTmREl0xRbl +9x8DYSjFyMsSoEJL+WuICI20MhjzdZ/EfwBPBZWcoxcCw7NTm6ogOSkrZvqdr16zktK1puEa+S1B +aYEUtLS17Yk9zvupnTVCRLEcFHOBzyoBNZox1S2PbYTfgE1X4z/FhHXaicYwu+uPyyIIoK6q8QNs +OktNCaUOcsZWayFCTiMlFGiudgp8DAdwZPmaL/YFOSbGDI8Zf0NebvRbFS/bYV3mZy8/CJT5YLSY +Mdp08YSTcU1f+2BY0fvEwW2JorsgH51xkcsymxM9Pn2SUjWskpSi0xjCfMfqr3YFFt1nJ8J+HAci +IfNAChs0B0QTwoRqjt8ZWr9/6x3iGjjRXK9HkmuAtTClyY3YqzGBH9/CZjfTk6mFhnll0g== +-----END CERTIFICATE----- + SwissSign Gold CA - G2 ====================== -----BEGIN CERTIFICATE----- @@ -2005,6 +2254,32 @@ hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0= -----END CERTIFICATE----- +S-TRUST Authentication and Encryption Root CA 2005 PN +===================================================== +-----BEGIN CERTIFICATE----- +MIIEezCCA2OgAwIBAgIQNxkY5lNUfBq1uMtZWts1tzANBgkqhkiG9w0BAQUFADCBrjELMAkGA1UE +BhMCREUxIDAeBgNVBAgTF0JhZGVuLVd1ZXJ0dGVtYmVyZyAoQlcpMRIwEAYDVQQHEwlTdHV0dGdh +cnQxKTAnBgNVBAoTIERldXRzY2hlciBTcGFya2Fzc2VuIFZlcmxhZyBHbWJIMT4wPAYDVQQDEzVT +LVRSVVNUIEF1dGhlbnRpY2F0aW9uIGFuZCBFbmNyeXB0aW9uIFJvb3QgQ0EgMjAwNTpQTjAeFw0w +NTA2MjIwMDAwMDBaFw0zMDA2MjEyMzU5NTlaMIGuMQswCQYDVQQGEwJERTEgMB4GA1UECBMXQmFk +ZW4tV3VlcnR0ZW1iZXJnIChCVykxEjAQBgNVBAcTCVN0dXR0Z2FydDEpMCcGA1UEChMgRGV1dHNj +aGVyIFNwYXJrYXNzZW4gVmVybGFnIEdtYkgxPjA8BgNVBAMTNVMtVFJVU1QgQXV0aGVudGljYXRp +b24gYW5kIEVuY3J5cHRpb24gUm9vdCBDQSAyMDA1OlBOMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA2bVKwdMz6tNGs9HiTNL1toPQb9UY6ZOvJ44TzbUlNlA0EmQpoVXhOmCTnijJ4/Ob +4QSwI7+Vio5bG0F/WsPoTUzVJBY+h0jUJ67m91MduwwA7z5hca2/OnpYH5Q9XIHV1W/fuJvS9eXL +g3KSwlOyggLrra1fFi2SU3bxibYs9cEv4KdKb6AwajLrmnQDaHgTncovmwsdvs91DSaXm8f1Xgqf +eN+zvOyauu9VjxuapgdjKRdZYgkqeQd3peDRF2npW932kKvimAoA0SVtnteFhy+S8dF2g08LOlk3 +KC8zpxdQ1iALCvQm+Z845y2kuJuJja2tyWp9iRe79n+Ag3rm7QIDAQABo4GSMIGPMBIGA1UdEwEB +/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMCkGA1UdEQQiMCCkHjAcMRowGAYDVQQDExFTVFJv +bmxpbmUxLTIwNDgtNTAdBgNVHQ4EFgQUD8oeXHngovMpttKFswtKtWXsa1IwHwYDVR0jBBgwFoAU +D8oeXHngovMpttKFswtKtWXsa1IwDQYJKoZIhvcNAQEFBQADggEBAK8B8O0ZPCjoTVy7pWMciDMD +pwCHpB8gq9Yc4wYfl35UvbfRssnV2oDsF9eK9XvCAPbpEW+EoFolMeKJ+aQAPzFoLtU96G7m1R08 +P7K9n3frndOMusDXtk3sU5wPBG7qNWdX4wple5A64U8+wwCSersFiXOMy6ZNwPv2AtawB6MDwidA +nwzkhYItr5pCHdDHjfhA7p0GVxzZotiAFP7hYy0yh9WUUpY6RsZxlj33mA6ykaqP2vROJAA5Veit +F7nTNCtKqUDMFypVZUF0Qn71wK/Ik63yGFs9iQzbRzkk+OBM8h+wPQrKBU6JIRrjKpms/H+h8Q8b +Hz2eBIPdltkdOpQ= +-----END CERTIFICATE----- + Microsec e-Szigno Root CA ========================= -----BEGIN CERTIFICATE----- @@ -2200,6 +2475,28 @@ dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU Cm26OWMohpLzGITY+9HPBVZkVw== -----END CERTIFICATE----- +ComSign CA +========== +-----BEGIN CERTIFICATE----- +MIIDkzCCAnugAwIBAgIQFBOWgxRVjOp7Y+X8NId3RDANBgkqhkiG9w0BAQUFADA0MRMwEQYDVQQD +EwpDb21TaWduIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTMy +MThaFw0yOTAzMTkxNTAyMThaMDQxEzARBgNVBAMTCkNvbVNpZ24gQ0ExEDAOBgNVBAoTB0NvbVNp +Z24xCzAJBgNVBAYTAklMMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8ORUaSvTx49q +ROR+WCf4C9DklBKK8Rs4OC8fMZwG1Cyn3gsqrhqg455qv588x26i+YtkbDqthVVRVKU4VbirgwTy +P2Q298CNQ0NqZtH3FyrV7zb6MBBC11PN+fozc0yz6YQgitZBJzXkOPqUm7h65HkfM/sb2CEJKHxN +GGleZIp6GZPKfuzzcuc3B1hZKKxC+cX/zT/npfo4sdAMx9lSGlPWgcxCejVb7Us6eva1jsz/D3zk +YDaHL63woSV9/9JLEYhwVKZBqGdTUkJe5DSe5L6j7KpiXd3DTKaCQeQzC6zJMw9kglcq/QytNuEM +rkvF7zuZ2SOzW120V+x0cAwqTwIDAQABo4GgMIGdMAwGA1UdEwQFMAMBAf8wPQYDVR0fBDYwNDAy +oDCgLoYsaHR0cDovL2ZlZGlyLmNvbXNpZ24uY28uaWwvY3JsL0NvbVNpZ25DQS5jcmwwDgYDVR0P +AQH/BAQDAgGGMB8GA1UdIwQYMBaAFEsBmz5WGmU2dst7l6qSBe4y5ygxMB0GA1UdDgQWBBRLAZs+ +VhplNnbLe5eqkgXuMucoMTANBgkqhkiG9w0BAQUFAAOCAQEA0Nmlfv4pYEWdfoPPbrxHbvUanlR2 +QnG0PFg/LUAlQvaBnPGJEMgOqnhPOAlXsDzACPw1jvFIUY0McXS6hMTXcpuEfDhOZAYnKuGntewI +mbQKDdSFc8gS4TXt8QUxHXOZDOuWyt3T5oWq8Ir7dcHyCTxlZWTzTNity4hp8+SDtwy9F1qWF8pb +/627HOkthIDYIb6FUtnUdLlphbpN7Sgy6/lhSuTENh4Z3G+EER+V9YMoGKgzkkMn3V0TBEVPh9VG +zT2ouvDzuFYkRes3x+F2T3I5GN9+dHLHcy056mDmrRGiVod7w2ia/viMcKjfZTL0pECMocJEAw6U +AGegcQCCSA== +-----END CERTIFICATE----- + ComSign Secured CA ================== -----BEGIN CERTIFICATE----- @@ -2748,6 +3045,22 @@ MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA== -----END CERTIFICATE----- +Verisign Class 1 Public Primary Certification Authority +======================================================= +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCED9pHoGc8JpK83P/uUii5N0wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow +XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAx +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDlGb9to1ZhLZlIcfZn3rmN67eehoAKkQ76OCWvRoiC5XOooJskXQ0fzGVuDLDQ +VoQYh5oGmxChc9+0WDlrbsH2FdWoqD+qEgaNMax/sDTXjzRniAnNFBHiTkVWaR94AoDa3EeRKbs2 +yWNcxeDXLYd7obcysHswuiovMaruo2fa2wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFgVKTk8d6Pa +XCUDfGD67gmZPCcQcMgMCeazh88K4hiWNWLMv5sneYlfycQJ9M61Hd8qveXbhpxoJeUwfLaJFf5n +0a3hUKw8fGJLj7qE1xIVGx/KXQ/BUpQqEZnae88MNhPVNdwQGVnqlMEAv3WP2fr9dgTbYruQagPZ +RjXZ+Hxb +-----END CERTIFICATE----- + Verisign Class 3 Public Primary Certification Authority ======================================================= -----BEGIN CERTIFICATE----- @@ -3329,3 +3642,254 @@ l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2 E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D 5EI= -----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2011 +======================================================= +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT +O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y +aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT +AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo +IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI +1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa +71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u +8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH +3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/ +MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8 +MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu +b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt +XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD +/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N +7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Trustis FPS Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290 +IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV +BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ +RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk +H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa +cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt +o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA +AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd +BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c +GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC +yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P +8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV +l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl +iB6XzCGcKQENZetX2fNXlrtIzYE= +-----END CERTIFICATE----- + +StartCom Certification Authority +================================ +-----BEGIN CERTIFICATE----- +MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu +ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 +NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk +LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg +U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y +o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ +Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d +eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt +2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z +6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ +osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ +untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc +UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT +37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ +Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0 +dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu +c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv +bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0 +aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t +L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG +cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5 +fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm +N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN +Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T +tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX +e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA +2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs +HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE +JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib +D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8= +-----END CERTIFICATE----- + +StartCom Certification Authority G2 +=================================== +-----BEGIN CERTIFICATE----- +MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE +ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O +o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG +4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi +Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul +Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs +O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H +vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L +nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS +FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa +z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ +KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K +2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk +J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+ +JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG +/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc +nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld +blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc +l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm +7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm +obp573PYtlNXLfbQ4ddI +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +EE Certification Centre Root CA +=============================== +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG +EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy +dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw +MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB +UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy +ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM +TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2 +rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw +93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN +P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ +MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF +BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj +xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM +lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU +3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM +dcGWxZ0= +-----END CERTIFICATE----- From fffa85e572c683e7a79659faea7f127f045243db Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Mon, 4 Mar 2013 16:16:58 -0800 Subject: [PATCH 0450/1398] Added support for following 307 temporary redirects. Fixes #133 References #172 --- features/s3/low_level/redirects.feature | 21 +++++++++++++++++++ .../s3/low_level/step_definitions/buckets.rb | 17 +++++++++++++++ lib/aws/core/client.rb | 15 +++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 features/s3/low_level/redirects.feature diff --git a/features/s3/low_level/redirects.feature b/features/s3/low_level/redirects.feature new file mode 100644 index 00000000000..c772a020c53 --- /dev/null +++ b/features/s3/low_level/redirects.feature @@ -0,0 +1,21 @@ +# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# language: en +@s3 @low_level @redirects +Feature: Working with Buckets + + Scenario: Create a bucket + Given I create a bucket in "EU" + When I write to an object in the bucket + Then I should follow redirects diff --git a/features/s3/low_level/step_definitions/buckets.rb b/features/s3/low_level/step_definitions/buckets.rb index 672dd3db88d..ebf1f396ed2 100644 --- a/features/s3/low_level/step_definitions/buckets.rb +++ b/features/s3/low_level/step_definitions/buckets.rb @@ -34,6 +34,23 @@ def create_bucket_high_level options = {} @bucket end +Given /^I create a bucket in "(.*?)"$/ do |location_constraint| + create_bucket_low_level(:location_constraint => location_constraint) +end + +When /^I write to an object in the bucket$/ do + @response = @s3.client.put_object( + :bucket_name => @bucket_name, + :key => 'foo', + :data => 'bar') +end + +Then /^I should follow redirects$/ do + @response.retry_count.should be > 0 + @response.http_request.host.should eq("#{@bucket_name}.s3-external-3.amazonaws.com") +end + + When /^I call create_bucket( asynchronously)?$/ do |async| create_bucket_low_level(:async => !async.to_s.strip.empty?) end diff --git a/lib/aws/core/client.rb b/lib/aws/core/client.rb index 14cdd115eaa..93158d83888 100644 --- a/lib/aws/core/client.rb +++ b/lib/aws/core/client.rb @@ -14,6 +14,7 @@ require 'json' require 'set' require 'yaml' +require 'uri' module AWS module Core @@ -273,6 +274,15 @@ def retry_server_errors &block def rebuild_http_request response credential_provider.refresh if expired_credentials?(response) response.rebuild_request + if redirected?(response) + loc = URI.parse(response.http_response.headers['location'].first) + AWS::Core::MetaUtils.extend_method(response.http_request, :host) do + loc.host + end + response.http_request.host = loc.host + response.http_request.port = loc.port + response.http_request.uri = loc.path + end response.retry_count += 1 end @@ -301,6 +311,7 @@ def retryable_error? response expired_credentials?(response) or response.network_error? or throttled?(response) or + redirected?(response) or response.error.kind_of?(Errors::ServerError) end @@ -318,6 +329,10 @@ def throttled? response response.error.code.to_s.match(/Throttling/i) end + def redirected? response + response.http_response.status == 307 + end + def return_or_raise options, &block response = yield unless options[:async] From 4e6538a941eb390e9e15db65a57d8881334c662f Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Fri, 8 Mar 2013 11:17:34 -0800 Subject: [PATCH 0451/1398] Added support for #cancel_stack_update to AWS::CloudFormation::Client. --- .../api_config/CloudFormation-2010-05-15.yml | 10 ++++++ lib/aws/cloud_formation/client.rb | 34 +++++++++++++------ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/lib/aws/api_config/CloudFormation-2010-05-15.yml b/lib/aws/api_config/CloudFormation-2010-05-15.yml index 53b5d42401d..e1841fb6578 100644 --- a/lib/aws/api_config/CloudFormation-2010-05-15.yml +++ b/lib/aws/api_config/CloudFormation-2010-05-15.yml @@ -14,6 +14,16 @@ --- :api_version: '2010-05-15' :operations: +- :name: CancelUpdateStack + :method: :cancel_update_stack + :inputs: + StackName: + - :string + - :required + :outputs: + :children: + CancelUpdateStackResult: + :ignore: true - :name: CreateStack :method: :create_stack :inputs: diff --git a/lib/aws/cloud_formation/client.rb b/lib/aws/cloud_formation/client.rb index e2e89e7d148..8eb9b147753 100644 --- a/lib/aws/cloud_formation/client.rb +++ b/lib/aws/cloud_formation/client.rb @@ -35,6 +35,13 @@ class Client < Core::QueryClient # client methods # + # @!method cancel_update_stack(options = {}) + # Calls the CancelUpdateStack API operation. + # @param [Hash] options + # * +:stack_name+ - *required* - (String) The name or the unique + # identifier associated with the stack. + # @return [Core::Response] + # @!method create_stack(options = {}) # Calls the CreateStack API operation. # @param [Hash] options @@ -48,10 +55,11 @@ class Client < Core::QueryClient # Conditional: You must pass TemplateBody or TemplateURL. If both are # passed, only TemplateBody is used. # * +:template_url+ - (String) Location of file containing the template - # body. The URL must point to a template located in an S3 bucket in - # the same region as the stack. For more information, go to the AWS - # CloudFormation User Guide. Conditional: You must pass TemplateURL - # or TemplateBody. If both are passed, only TemplateBody is used. + # body. The URL must point to a template (max size: 307,200 bytes) + # located in an S3 bucket in the same region as the stack. For more + # information, go to the AWS CloudFormation User Guide. Conditional: + # You must pass TemplateURL or TemplateBody. If both are passed, only + # TemplateBody is used. # * +:parameters+ - (Array) A list of Parameter structures that # specify input parameters for the stack. # * +:parameter_key+ - (String) The key associated with the @@ -154,7 +162,9 @@ class Client < Core::QueryClient # Calls the DescribeStackResources API operation. # @param [Hash] options # * +:stack_name+ - (String) The name or the unique identifier - # associated with the stack. Default: There is no default value. + # associated with the stack. Required: Conditional. If you do not + # specify StackName, you must specify PhysicalResourceId. Default: + # There is no default value. # * +:logical_resource_id+ - (String) The logical name of the resource # as specified in the template. Default: There is no default value. # * +:physical_resource_id+ - (String) The name or unique identifier @@ -163,8 +173,9 @@ class Client < Core::QueryClient # Cloud (EC2) instance, PhysicalResourceId corresponds to the # InstanceId. You can pass the EC2 InstanceId to # DescribeStackResources to find which stack the instance belongs to - # and what other resources are part of the stack. Default: There is - # no default value. + # and what other resources are part of the stack. Required: + # Conditional. If you do not specify PhysicalResourceId, you must + # specify StackName. Default: There is no default value. # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -332,10 +343,11 @@ class Client < Core::QueryClient # Conditional: You must pass TemplateURL or TemplateBody. If both are # passed, only TemplateBody is used. # * +:template_url+ - (String) Location of file containing the template - # body. The URL must point to a template located in an S3 bucket in - # the same region as the stack. For more information, go to the AWS - # CloudFormation User Guide. Conditional: You must pass TemplateURL - # or TemplateBody. If both are passed, only TemplateBody is used. + # body. The URL must point to a template (max size: 307,200 bytes) + # located in an S3 bucket in the same region as the stack. For more + # information, go to the AWS CloudFormation User Guide. Conditional: + # You must pass TemplateURL or TemplateBody. If both are passed, only + # TemplateBody is used. # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: From 94aa3a03c115565aed951301f67d5b0b3211cc93 Mon Sep 17 00:00:00 2001 From: Mike Vastola Date: Sat, 9 Mar 2013 01:19:30 -0500 Subject: [PATCH 0452/1398] Fixed typo in docs citing non-existent method.. Specifically, S3::Bucket#disable_versioning. In reality, it's #suspend_versioning. --- lib/aws/s3/bucket.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aws/s3/bucket.rb b/lib/aws/s3/bucket.rb index 68279c92316..ed3c75ced89 100644 --- a/lib/aws/s3/bucket.rb +++ b/lib/aws/s3/bucket.rb @@ -188,7 +188,7 @@ class S3 # # bucket.versioning_enabled? #=> false # bucket.enable_versioning - # # there is also a #disable_versioning method + # # there is also a #suspend_versioning method # # obj = bucket.objects['my-obj'] # obj.write('a') From df0dbadb232e8daf00b59e00523edafeae064f62 Mon Sep 17 00:00:00 2001 From: Mike Ferrier Date: Sun, 10 Mar 2013 00:36:14 +0900 Subject: [PATCH 0453/1398] CORSRuleCollection delete_if can delete all rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If CORSRuleCollection#delete_if matched all the rules, you'd get:   ArgumentError - expected one or more rules:     (gem) aws-sdk-1.8.3.1/lib/aws/s3/cors_rule_collection.rb:101:in `set' So if all the rules are deleted, call #clear instead. --- lib/aws/s3/cors_rule_collection.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/aws/s3/cors_rule_collection.rb b/lib/aws/s3/cors_rule_collection.rb index d7503c446f9..7773ef08d4b 100644 --- a/lib/aws/s3/cors_rule_collection.rb +++ b/lib/aws/s3/cors_rule_collection.rb @@ -148,7 +148,11 @@ def delete_if &block self.each do |rule| rules << rule unless yield(rule) end - self.set(*rules) + if rules.any? + self.set(*rules) + else + self.clear + end end # Removes all CORS rules attached to this bucket. From 7794c64c4690832fc8cef45119a7f4c860b49c4b Mon Sep 17 00:00:00 2001 From: Mike Ferrier Date: Sun, 10 Mar 2013 00:40:59 +0900 Subject: [PATCH 0454/1398] Better fix for the delete_if bug Actually, this works better since #set checks for [[]]. --- lib/aws/s3/cors_rule_collection.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/aws/s3/cors_rule_collection.rb b/lib/aws/s3/cors_rule_collection.rb index 7773ef08d4b..2be379a678b 100644 --- a/lib/aws/s3/cors_rule_collection.rb +++ b/lib/aws/s3/cors_rule_collection.rb @@ -148,11 +148,7 @@ def delete_if &block self.each do |rule| rules << rule unless yield(rule) end - if rules.any? - self.set(*rules) - else - self.clear - end + self.set(rules) end # Removes all CORS rules attached to this bucket. From 7119443e3b89503e00ec2d908cb8fcfcb95ce20f Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Mon, 11 Mar 2013 09:51:09 -0700 Subject: [PATCH 0455/1398] Updated the EC2 API configuration to the 2013-02-01 API version. --- ...{EC2-2012-12-01.yml => EC2-2013-02-01.yml} | 120 +++++++++++++++--- lib/aws/ec2/client.rb | 87 +++++++++++-- 2 files changed, 181 insertions(+), 26 deletions(-) rename lib/aws/api_config/{EC2-2012-12-01.yml => EC2-2013-02-01.yml} (97%) diff --git a/lib/aws/api_config/EC2-2012-12-01.yml b/lib/aws/api_config/EC2-2013-02-01.yml similarity index 97% rename from lib/aws/api_config/EC2-2012-12-01.yml rename to lib/aws/api_config/EC2-2013-02-01.yml index f3c46cf68a0..44db104e760 100644 --- a/lib/aws/api_config/EC2-2012-12-01.yml +++ b/lib/aws/api_config/EC2-2013-02-01.yml @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. --- -:api_version: '2012-12-01' +:api_version: '2013-02-01' :operations: - :name: ActivateLicense :method: :activate_license @@ -371,6 +371,23 @@ - :string - :required :outputs: {} +- :name: CopyImage + :method: :copy_image + :inputs: + SourceRegion: + - :string + - :required + SourceImageId: + - :string + - :required + Name: + - :string + - :required + Description: + - :string + ClientToken: + - :string + :outputs: {} - :name: CopySnapshot :method: :copy_snapshot :inputs: @@ -799,12 +816,6 @@ item: :rename: :propagating_vgw_set :list: true - propagatedRouteSet: - :ignore: true - :children: - item: - :rename: :propagated_route_set - :list: true - :name: CreateSecurityGroup :method: :create_security_group :inputs: @@ -864,6 +875,10 @@ :children: availableIpAddressCount: :type: :integer + defaultForAz: + :type: :boolean + mapPublicIpOnLaunch: + :type: :boolean tagSet: :ignore: true :children: @@ -945,6 +960,8 @@ item: :rename: :tag_set :list: true + isDefault: + :type: :boolean - :name: CreateVpnConnection :method: :create_vpn_connection :inputs: @@ -1205,6 +1222,28 @@ - :string - :required :outputs: {} +- :name: DescribeAccountAttributes + :method: :describe_account_attributes + :inputs: + AttributeName: + - :list: + - :string + - :rename: attributeNames + :outputs: + :children: + accountAttributeSet: + :ignore: true + :children: + item: + :rename: :account_attribute_set + :list: true + :children: + attributeValueSet: + :ignore: true + :children: + item: + :rename: :attribute_value_set + :list: true - :name: DescribeAddresses :method: :describe_addresses :inputs: @@ -1770,6 +1809,15 @@ :type: :time deleteOnTermination: :type: :boolean + privateIpAddressesSet: + :ignore: true + :children: + item: + :rename: :private_ip_addresses_set + :list: true + :children: + primary: + :type: :boolean ebsOptimized: :type: :boolean :index: @@ -2330,12 +2378,6 @@ item: :rename: :propagating_vgw_set :list: true - propagatedRouteSet: - :ignore: true - :children: - item: - :rename: :propagated_route_set - :list: true - :name: DescribeSecurityGroups :method: :describe_security_groups :inputs: @@ -2662,6 +2704,10 @@ :children: availableIpAddressCount: :type: :integer + defaultForAz: + :type: :boolean + mapPublicIpOnLaunch: + :type: :boolean tagSet: :ignore: true :children: @@ -2822,6 +2868,26 @@ :index: :key: :volume_id :name: :volume_index +- :name: DescribeVpcAttribute + :method: :describe_vpc_attribute + :inputs: + VpcId: + - :string + - :required + EnableDnsSupport: + - :string + EnableDnsHostnames: + - :string + :outputs: + :children: + enableDnsSupport: + :children: + value: + :type: :boolean + enableDnsHostnames: + :children: + value: + :type: :boolean - :name: DescribeVpcs :method: :describe_vpcs :inputs: @@ -2854,6 +2920,8 @@ item: :rename: :tag_set :list: true + isDefault: + :type: :boolean - :name: DescribeVpnConnections :method: :describe_vpn_connections :inputs: @@ -3445,6 +3513,21 @@ AutoEnableIO: - :boolean :outputs: {} +- :name: ModifyVpcAttribute + :method: :modify_vpc_attribute + :inputs: + VpcId: + - :string + - :rename: VpcId + EnableDnsSupport: + - :structure: + Value: + - :boolean + EnableDnsHostnames: + - :structure: + Value: + - :boolean + :outputs: {} - :name: MonitorInstances :method: :monitor_instances :inputs: @@ -3988,8 +4071,6 @@ - :rename: SecurityGroupIds UserData: - :string - AddressingType: - - :string InstanceType: - :string Placement: @@ -4168,6 +4249,15 @@ :type: :time deleteOnTermination: :type: :boolean + privateIpAddressesSet: + :ignore: true + :children: + item: + :rename: :private_ip_addresses_set + :list: true + :children: + primary: + :type: :boolean ebsOptimized: :type: :boolean - :name: StartInstances diff --git a/lib/aws/ec2/client.rb b/lib/aws/ec2/client.rb index c3b0016f420..c80dbae0969 100644 --- a/lib/aws/ec2/client.rb +++ b/lib/aws/ec2/client.rb @@ -391,6 +391,19 @@ class Client < Core::QueryClient # a hash with the following structure: # * +:owner_id+ - (String) + # @!method copy_image(options = {}) + # Calls the CopyImage API operation. + # @param [Hash] options + # * +:source_region+ - *required* - (String) + # * +:source_image_id+ - *required* - (String) + # * +:name+ - *required* - (String) + # * +:description+ - (String) + # * +:client_token+ - (String) + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:image_id+ - (String) + # @!method copy_snapshot(options = {}) # Calls the CopySnapshot API operation. # @param [Hash] options @@ -649,6 +662,7 @@ class Client < Core::QueryClient # * +:value+ - (String) # * +:private_ip_addresses_set+ - (Array) # * +:private_ip_address+ - (String) + # * +:private_dns_name+ - (String) # * +:primary+ - (Boolean) # * +:association+ - (Hash) # * +:public_ip+ - (String) @@ -741,11 +755,6 @@ class Client < Core::QueryClient # * +:value+ - (String) # * +:propagating_vgw_set+ - (Array) # * +:gateway_id+ - (String) - # * +:propagated_route_set+ - (Array) - # * +:destination_cidr_block+ - (String) - # * +:gateway_id+ - (String) - # * +:status+ - (String) - # * +:source_id+ - (String) # @!method create_security_group(options = {}) # Calls the CreateSecurityGroup API operation. @@ -819,6 +828,8 @@ class Client < Core::QueryClient # * +:cidr_block+ - (String) # * +:available_ip_address_count+ - (Integer) # * +:availability_zone+ - (String) + # * +:default_for_az+ - (Boolean) + # * +:map_public_ip_on_launch+ - (Boolean) # * +:tag_set+ - (Array) # * +:key+ - (String) # * +:value+ - (String) @@ -889,6 +900,7 @@ class Client < Core::QueryClient # * +:key+ - (String) # * +:value+ - (String) # * +:instance_tenancy+ - (String) + # * +:is_default+ - (Boolean) # @!method create_vpn_connection(options = {}) # Calls the CreateVpnConnection API operation. @@ -1123,6 +1135,18 @@ class Client < Core::QueryClient # deregister. # @return [Core::Response] + # @!method describe_account_attributes(options = {}) + # Calls the DescribeAccountAttributes API operation. + # @param [Hash] options + # * +:attribute_names+ - (Array) + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:account_attribute_set+ - (Array) + # * +:attribute_name+ - (String) + # * +:attribute_value_set+ - (Array) + # * +:attribute_value+ - (String) + # @!method describe_addresses(options = {}) # Calls the DescribeAddresses API operation. # @param [Hash] options @@ -1599,7 +1623,16 @@ class Client < Core::QueryClient # * +:delete_on_termination+ - (Boolean) # * +:association+ - (Hash) # * +:public_ip+ - (String) + # * +:public_dns_name+ - (String) # * +:ip_owner_id+ - (String) + # * +:private_ip_addresses_set+ - (Array) + # * +:private_ip_address+ - (String) + # * +:private_dns_name+ - (String) + # * +:primary+ - (Boolean) + # * +:association+ - (Hash) + # * +:public_ip+ - (String) + # * +:public_dns_name+ - (String) + # * +:ip_owner_id+ - (String) # * +:iam_instance_profile+ - (Hash) # * +:arn+ - (String) # * +:id+ - (String) @@ -1786,6 +1819,7 @@ class Client < Core::QueryClient # * +:value+ - (String) # * +:private_ip_addresses_set+ - (Array) # * +:private_ip_address+ - (String) + # * +:private_dns_name+ - (String) # * +:primary+ - (Boolean) # * +:association+ - (Hash) # * +:public_ip+ - (String) @@ -1981,11 +2015,6 @@ class Client < Core::QueryClient # * +:value+ - (String) # * +:propagating_vgw_set+ - (Array) # * +:gateway_id+ - (String) - # * +:propagated_route_set+ - (Array) - # * +:destination_cidr_block+ - (String) - # * +:gateway_id+ - (String) - # * +:status+ - (String) - # * +:source_id+ - (String) # @!method describe_security_groups(options = {}) # Calls the DescribeSecurityGroups API operation. @@ -2233,6 +2262,8 @@ class Client < Core::QueryClient # * +:cidr_block+ - (String) # * +:available_ip_address_count+ - (Integer) # * +:availability_zone+ - (String) + # * +:default_for_az+ - (Boolean) + # * +:map_public_ip_on_launch+ - (Boolean) # * +:tag_set+ - (Array) # * +:key+ - (String) # * +:value+ - (String) @@ -2337,6 +2368,21 @@ class Client < Core::QueryClient # * +:volume_type+ - (String) # * +:iops+ - (Integer) + # @!method describe_vpc_attribute(options = {}) + # Calls the DescribeVpcAttribute API operation. + # @param [Hash] options + # * +:vpc_id+ - *required* - (String) + # * +:enable_dns_support+ - (String) + # * +:enable_dns_hostnames+ - (String) + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:vpc_id+ - (String) + # * +:enable_dns_support+ - (Hash) + # * +:value+ - (Boolean) + # * +:enable_dns_hostnames+ - (Hash) + # * +:value+ - (Boolean) + # @!method describe_vpcs(options = {}) # Calls the DescribeVpcs API operation. # @param [Hash] options @@ -2360,6 +2406,7 @@ class Client < Core::QueryClient # * +:key+ - (String) # * +:value+ - (String) # * +:instance_tenancy+ - (String) + # * +:is_default+ - (Boolean) # @!method describe_vpn_connections(options = {}) # Calls the DescribeVpnConnections API operation. @@ -2838,6 +2885,16 @@ class Client < Core::QueryClient # * +:auto_enable_io+ - (Boolean) # @return [Core::Response] + # @!method modify_vpc_attribute(options = {}) + # Calls the ModifyVpcAttribute API operation. + # @param [Hash] options + # * +:vpc_id+ - (String) + # * +:enable_dns_support+ - (Hash) + # * +:value+ - (Boolean) Boolean value + # * +:enable_dns_hostnames+ - (Hash) + # * +:value+ - (Boolean) Boolean value + # @return [Core::Response] + # @!method monitor_instances(options = {}) # Calls the MonitorInstances API operation. # @param [Hash] options @@ -3298,7 +3355,6 @@ class Client < Core::QueryClient # * +:security_group_ids+ - (Array) # * +:user_data+ - (String) Specifies additional information to make # available to the instance(s). - # * +:addressing_type+ - (String) # * +:instance_type+ - (String) Specifies the instance type for the # launched instances. # * +:placement+ - (Hash) Specifies the placement constraints @@ -3465,7 +3521,16 @@ class Client < Core::QueryClient # * +:delete_on_termination+ - (Boolean) # * +:association+ - (Hash) # * +:public_ip+ - (String) + # * +:public_dns_name+ - (String) # * +:ip_owner_id+ - (String) + # * +:private_ip_addresses_set+ - (Array) + # * +:private_ip_address+ - (String) + # * +:private_dns_name+ - (String) + # * +:primary+ - (Boolean) + # * +:association+ - (Hash) + # * +:public_ip+ - (String) + # * +:public_dns_name+ - (String) + # * +:ip_owner_id+ - (String) # * +:iam_instance_profile+ - (Hash) # * +:arn+ - (String) # * +:id+ - (String) From c74902e9bb32f13ce089f5b70afb48607bdd1605 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Mon, 11 Mar 2013 10:57:09 -0700 Subject: [PATCH 0456/1398] Bumped AWS::EC2::Client api version. --- lib/aws/ec2/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aws/ec2/client.rb b/lib/aws/ec2/client.rb index c80dbae0969..19349ba557c 100644 --- a/lib/aws/ec2/client.rb +++ b/lib/aws/ec2/client.rb @@ -3615,7 +3615,7 @@ class Client < Core::QueryClient # end client methods # - define_client_methods('2012-12-01') + define_client_methods('2013-02-01') end end From 77444dd208ca14459d00c0dec76b1c6964a470bd Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Mon, 11 Mar 2013 11:02:12 -0700 Subject: [PATCH 0457/1398] Bump version to 1.8.4 --- ChangeLog | 114 +++++++++++++++++++++++++++++++++++++++++++++ lib/aws/version.rb | 2 +- 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 60290d50bd4..026f816e104 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,119 @@ +2013-03-10 Mike Ferrier + + * lib/aws/s3/cors_rule_collection.rb: Better fix for the delete_if bug + + Actually, this works better since #set checks for [[]]. + * lib/aws/s3/cors_rule_collection.rb: CORSRuleCollection delete_if can + delete all rules + + If CORSRuleCollection#delete_if matched all the rules, you'd get: + +   ArgumentError - expected one or more rules: +     (gem) aws-sdk-1.8.3.1/lib/aws/s3/cors_rule_collection.rb:101:in `set' + + So if all the rules are deleted, call #clear instead. +2013-03-09 Mike Vastola + + * lib/aws/s3/bucket.rb: Fixed typo in docs citing non-existent method.. + + Specifically, S3::Bucket#disable_versioning. + In reality, it's #suspend_versioning. + +2013-03-08 Trevor Rowe + + * lib/aws/api_config/CloudFormation-2010-05-15.yml, + lib/aws/cloud_formation/client.rb: Added support for #cancel_stack_update to + AWS::CloudFormation::Client. + +2013-03-04 Trevor Rowe + + * features/s3/low_level/redirects.feature, + features/s3/low_level/step_definitions/buckets.rb, lib/aws/core/client.rb: + Added support for following 307 temporary redirects. + + Fixes #133 + References #172 + + * ca-bundle.crt: Updated bundled ca-bundle.crt file. + +2013-03-02 Trevor Rowe + + * lib/aws/dynamo_db.rb: Removed old and no longer valid documentation about + using session tokens with DynamoDB. + +2013-02-28 Carl Parker + + * lib/aws/core.rb: fix endpoint in docs for elastictranscoder + + * features/auto_scaling/step_definitions/groups.rb: Fixed broken auto + scaling feature. + + * spec/aws/auto_scaling/scaling_policy_colletion_spec.rb, + spec/aws/auto_scaling/scaling_policy_spec.rb: Tests for the + :min_adjustment_step option + + * lib/aws/auto_scaling/scaling_policy.rb, + lib/aws/auto_scaling/scaling_policy_options.rb: Add :min_adjustment_step + option for AutoScaling policies + +2013-02-27 Trevor Rowe + + * lib/aws/sqs/request.rb: Updated SQS region logic to fail gracefully when + the QueueUrl is not valid. + + * features/sqs/queues.feature, features/sqs/step_definitions/messages.rb, + features/sqs/step_definitions/queues.rb, lib/aws/record/abstract_base.rb, + lib/aws/sqs/request.rb: Now extracting the region from the QueueUrl param + when signing SQS requests. + + Fixes #179 + + * .yardopts, Gemfile, doc-src/templates/default/layout/html/footer.erb: Add + analytics script tags for documentation + + * lib/aws/auto_scaling.rb, lib/aws/cloud_formation.rb, + lib/aws/cloud_front.rb, lib/aws/cloud_search.rb, lib/aws/cloud_watch.rb, + lib/aws/data_pipeline.rb, lib/aws/dynamo_db.rb, lib/aws/ec2.rb, + lib/aws/elastic_beanstalk.rb, lib/aws/elasticache.rb, lib/aws/elb.rb, + lib/aws/emr.rb, lib/aws/glacier.rb, lib/aws/iam.rb, + lib/aws/import_export.rb, lib/aws/rds.rb, lib/aws/route_53.rb, + lib/aws/simple_db.rb, lib/aws/simple_email_service.rb, + lib/aws/simple_workflow.rb, lib/aws/sns.rb, lib/aws/sqs.rb, + lib/aws/storage_gateway.rb, lib/aws/sts.rb: Document client attributes on + service classes + +2013-02-26 Andy Brody + + * lib/aws/route_53/resource_record_set.rb: Don't include resource_records if + it's empty. + + This fixes a bug where attempting to delete an alias record set would + fail because resource records was passed as the empty list. + + * lib/aws/route_53/hosted_zone.rb: s/recoed/record/ + + * lib/aws/route_53/resource_record_set.rb: Memoize list calls in resource + record set updates. + + * lib/aws/route_53/resource_record_set.rb: Fix typo. + + * Gemfile: Locked rspec dependency to 2.12 for now. + + * lib/aws/s3/client.rb, spec/aws/s3/client_spec.rb: Added 3 addition headers + returned by AWS::S3::Client#head_object and #get_object. + + Fixes #86 + +2013-02-25 Loren Segal + + * .travis.yml: Add Ruby 2.0 to build matrix on Travis CI + + * spec/aws/core/data_spec.rb: Fix test for Ruby 2.0 support + 2013-02-22 Trevor Rowe + * ChangeLog, lib/aws/version.rb: Bump version to 1.8.3.1 + * lib/aws/ec2/image.rb, spec/aws/ec2/image_spec.rb: Fixed a regression in AWS::EC2::Image#block_device_mappings. diff --git a/lib/aws/version.rb b/lib/aws/version.rb index c23fd5d043b..eb8d4ddff8d 100644 --- a/lib/aws/version.rb +++ b/lib/aws/version.rb @@ -14,5 +14,5 @@ module AWS # Current version of the AWS SDK for Ruby - VERSION = '1.8.3.1' + VERSION = '1.8.4' end From 715824f98951e9ebff247e57bfc96a6af7fc9126 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Mon, 11 Mar 2013 15:00:19 -0700 Subject: [PATCH 0458/1398] Removed and unecessary service call from DynamoDB features. --- features/dynamo_db/step_definitions/dynamo_db.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/features/dynamo_db/step_definitions/dynamo_db.rb b/features/dynamo_db/step_definitions/dynamo_db.rb index fd169d45be7..f34693e6926 100644 --- a/features/dynamo_db/step_definitions/dynamo_db.rb +++ b/features/dynamo_db/step_definitions/dynamo_db.rb @@ -14,13 +14,9 @@ require 'digest/md5' Before("@dynamo_db") do - - session = AWS::STS.new.new_session - @dynamo_db = AWS::DynamoDB.new @tables = [] @created_tables = [] - end After("@dynamo_db") do |scenario| From 3feef027f8c60149db32c3b6e93601aa1561831f Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Tue, 12 Mar 2013 08:11:46 -0700 Subject: [PATCH 0459/1398] Added missing documentation for the :expires option to S3Object#write. --- lib/aws/s3/s3_object.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/aws/s3/s3_object.rb b/lib/aws/s3/s3_object.rb index e13a7f02b11..105f5ea7b3c 100644 --- a/lib/aws/s3/s3_object.rb +++ b/lib/aws/s3/s3_object.rb @@ -585,6 +585,9 @@ def versions # client-side encryption materials in a separate object in S3 # instead of in the object metadata. # + # @option options [String] :expires The date and time at which the + # object is no longer cacheable. + # # @return [S3Object, ObjectVersion] If the bucket has versioning # enabled, this methods returns an {ObjectVersion}, otherwise # this method returns +self+. From 769e03264122e5c75c190c5715ea46d29d05b5fe Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Tue, 12 Mar 2013 09:05:46 -0700 Subject: [PATCH 0460/1398] EC2::SnapshotCollection now populates snapshots when enumerating. --- lib/aws/ec2/snapshot_collection.rb | 5 +++-- spec/aws/ec2/snapshot_collection_spec.rb | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/aws/ec2/snapshot_collection.rb b/lib/aws/ec2/snapshot_collection.rb index d746e9835a1..4b5f10d8f47 100644 --- a/lib/aws/ec2/snapshot_collection.rb +++ b/lib/aws/ec2/snapshot_collection.rb @@ -50,8 +50,9 @@ def each(&block) opts[:restorable_by_user_ids] = @restorable_by.map { |id| id.to_s } unless @restorable_by.empty? resp = filtered_request(:describe_snapshots, opts) - resp.snapshot_set.each do |v| - snapshot = Snapshot.new(v.snapshot_id, :config => config) + resp[:snapshot_set].each do |details| + snapshot = Snapshot.new_from(:describe_snapshots, details, + details[:snapshot_id], :config => config) yield(snapshot) end nil diff --git a/spec/aws/ec2/snapshot_collection_spec.rb b/spec/aws/ec2/snapshot_collection_spec.rb index b304f596d12..24e17ceca50 100644 --- a/spec/aws/ec2/snapshot_collection_spec.rb +++ b/spec/aws/ec2/snapshot_collection_spec.rb @@ -27,11 +27,10 @@ class EC2 let(:client_method) { :describe_snapshots } def stub_two_members(resp) - resp.stub(:snapshot_set). - and_return([double("snapshot 1", - :snapshot_id => "snap-123"), - double("snapshot 2", - :snapshot_id => "snap-321")]) + resp.data[:snapshot_set] = [ + { :snapshot_id => 'snap-123' }, + { :snapshot_id => 'snap-321' }, + ] end it_should_behave_like "a tagged ec2 collection" From cbcaf9e306f0af5989f8a2aa7a89fffddbbbd74e Mon Sep 17 00:00:00 2001 From: Mahito Date: Wed, 13 Mar 2013 02:37:21 +0900 Subject: [PATCH 0461/1398] Changed String to Boolean with type of:dynamo_db_retry_throughput_errors --- lib/aws/core.rb | 4 ++-- lib/aws/core/configuration.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/aws/core.rb b/lib/aws/core.rb index 484054e8101..f8a6afc787d 100644 --- a/lib/aws/core.rb +++ b/lib/aws/core.rb @@ -249,7 +249,7 @@ class << self # @option options [String] :dynamo_db_endpoint ('dynamodb.amazonaws.com') # The service endpoint for Amazon DynamoDB. # - # @option options [String] :dynamo_db_retry_throughput_errors (true) When + # @option options [Boolean] :dynamo_db_retry_throughput_errors (true) When # true, AWS::DynamoDB::Errors::ProvisionedThroughputExceededException # errors will be retried. # @@ -258,7 +258,7 @@ class << self # # @option options [String] :elasticache_endpoint ('elasticache.us-east-1.amazonaws.com') # - # @option options [String] :elastic_beanstalk_endpoint ('elasticbeanstalk.us-east-1.amazonaws.com') + # @option options [String] :elastic_beanstalk_endpoint ('elasticbeanstalk.us-east-1.amazonaws.com') # The service endpoint for AWS Elastic Beanstalk. # # @option options [String] :elb_endpoint ('elasticloadbalancing.us-east-1.amazonaws.com') diff --git a/lib/aws/core/configuration.rb b/lib/aws/core/configuration.rb index 23db43ef274..fa8a38a47ee 100644 --- a/lib/aws/core/configuration.rb +++ b/lib/aws/core/configuration.rb @@ -87,7 +87,7 @@ module Core # @attr_reader [String] dynamo_db_endpoint ('dynamodb.us-east-1.amazonaws.com') # The service endpoint for Amazon DynamoDB. # - # @attr_reader [String] dynamo_db_retry_throughput_errors (true) When + # @attr_reader [Boolean] dynamo_db_retry_throughput_errors (true) When # true, AWS::DynamoDB::Errors::ProvisionedThroughputExceededException # errors will be retried. # @@ -96,7 +96,7 @@ module Core # # @attr_reader [String] elasticache_endpoint ('elasticache.us-east-1.amazonaws.com') # - # @attr_reader [String] elastic_beanstalk_endpoint ('elasticbeanstalk.us-east-1.amazonaws.com') + # @attr_reader [String] elastic_beanstalk_endpoint ('elasticbeanstalk.us-east-1.amazonaws.com') # The service endpoint for AWS Elastic Beanstalk. # # @attr_reader [String] elb_endpoint ('elasticloadbalancing.us-east-1.amazonaws.com') From 3716bec59ca62e033f6db18ea7f1c0c9a0789c53 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Tue, 12 Mar 2013 14:31:45 -0700 Subject: [PATCH 0462/1398] Updated RDS client to the latest API version (2013-02-12). --- lib/aws/api_config/RDS-2013-02-12.yml | 2377 +++++++++++++++++++++++++ lib/aws/rds/client.rb | 492 ++++- 2 files changed, 2848 insertions(+), 21 deletions(-) create mode 100644 lib/aws/api_config/RDS-2013-02-12.yml diff --git a/lib/aws/api_config/RDS-2013-02-12.yml b/lib/aws/api_config/RDS-2013-02-12.yml new file mode 100644 index 00000000000..7bf8b0b7ee7 --- /dev/null +++ b/lib/aws/api_config/RDS-2013-02-12.yml @@ -0,0 +1,2377 @@ +# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +--- +:api_version: '2013-02-12' +:operations: +- :name: AddSourceIdentifierToSubscription + :method: :add_source_identifier_to_subscription + :inputs: + SubscriptionName: + - :string + - :required + SourceIdentifier: + - :string + - :required + :outputs: + :children: + AddSourceIdentifierToSubscriptionResult: + :ignore: true + :children: + EventSubscription: + :children: + SourceIdsList: + :ignore: true + :children: + SourceId: + :rename: :source_ids_list + :list: true + EventCategoriesList: + :ignore: true + :children: + EventCategory: + :rename: :event_categories_list + :list: true + Enabled: + :type: :boolean + :ignore: true +- :name: AddTagsToResource + :method: :add_tags_to_resource + :inputs: + ResourceName: + - :string + - :required + Tags: + - :membered_list: + - :structure: + Key: + - :string + Value: + - :string + - :required + :outputs: + :children: + AddTagsToResourceResult: + :ignore: true +- :name: AuthorizeDBSecurityGroupIngress + :method: :authorize_db_security_group_ingress + :inputs: + DBSecurityGroupName: + - :string + - :required + CIDRIP: + - :string + EC2SecurityGroupName: + - :string + EC2SecurityGroupId: + - :string + EC2SecurityGroupOwnerId: + - :string + :outputs: + :children: + AuthorizeDBSecurityGroupIngressResult: + :ignore: true + :children: + DBSecurityGroup: + :children: + EC2SecurityGroups: + :ignore: true + :children: + EC2SecurityGroup: + :rename: :ec2_security_groups + :list: true + IPRanges: + :ignore: true + :children: + IPRange: + :rename: :ip_ranges + :list: true + :ignore: true +- :name: CopyDBSnapshot + :method: :copy_db_snapshot + :inputs: + SourceDBSnapshotIdentifier: + - :string + - :required + TargetDBSnapshotIdentifier: + - :string + - :required + :outputs: + :children: + CopyDBSnapshotResult: + :ignore: true + :children: + DBSnapshot: + :children: + SnapshotCreateTime: + :type: :time + AllocatedStorage: + :type: :integer + Port: + :type: :integer + InstanceCreateTime: + :type: :time + Iops: + :type: :integer + :ignore: true +- :name: CreateDBInstance + :method: :create_db_instance + :inputs: + DBName: + - :string + DBInstanceIdentifier: + - :string + - :required + AllocatedStorage: + - :integer + - :required + DBInstanceClass: + - :string + - :required + Engine: + - :string + - :required + MasterUsername: + - :string + - :required + MasterUserPassword: + - :string + - :required + DBSecurityGroups: + - :membered_list: + - :string + VpcSecurityGroupIds: + - :membered_list: + - :string + AvailabilityZone: + - :string + DBSubnetGroupName: + - :string + PreferredMaintenanceWindow: + - :string + DBParameterGroupName: + - :string + BackupRetentionPeriod: + - :integer + PreferredBackupWindow: + - :string + Port: + - :integer + MultiAZ: + - :boolean + EngineVersion: + - :string + AutoMinorVersionUpgrade: + - :boolean + LicenseModel: + - :string + Iops: + - :integer + OptionGroupName: + - :string + CharacterSetName: + - :string + PubliclyAccessible: + - :boolean + :outputs: + :children: + CreateDBInstanceResult: + :ignore: true + :children: + DBInstance: + :children: + Endpoint: + :children: + Port: + :type: :integer + AllocatedStorage: + :type: :integer + InstanceCreateTime: + :type: :time + BackupRetentionPeriod: + :type: :integer + DBSecurityGroups: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_groups + :list: true + VpcSecurityGroups: + :ignore: true + :children: + VpcSecurityGroupMembership: + :rename: :vpc_security_groups + :list: true + DBParameterGroups: + :ignore: true + :children: + DBParameterGroup: + :rename: :db_parameter_groups + :list: true + DBSubnetGroup: + :children: + Subnets: + :ignore: true + :children: + Subnet: + :rename: :subnets + :list: true + :children: + SubnetAvailabilityZone: + :children: + ProvisionedIopsCapable: + :type: :boolean + PendingModifiedValues: + :children: + AllocatedStorage: + :type: :integer + Port: + :type: :integer + BackupRetentionPeriod: + :type: :integer + MultiAZ: + :type: :boolean + Iops: + :type: :integer + LatestRestorableTime: + :type: :time + MultiAZ: + :type: :boolean + AutoMinorVersionUpgrade: + :type: :boolean + ReadReplicaDBInstanceIdentifiers: + :ignore: true + :children: + ReadReplicaDBInstanceIdentifier: + :rename: :read_replica_db_instance_identifiers + :list: true + Iops: + :type: :integer + OptionGroupMemberships: + :ignore: true + :children: + OptionGroupMembership: + :rename: :option_group_memberships + :list: true + PubliclyAccessible: + :type: :boolean + :ignore: true +- :name: CreateDBInstanceReadReplica + :method: :create_db_instance_read_replica + :inputs: + DBInstanceIdentifier: + - :string + - :required + SourceDBInstanceIdentifier: + - :string + - :required + DBInstanceClass: + - :string + AvailabilityZone: + - :string + Port: + - :integer + AutoMinorVersionUpgrade: + - :boolean + Iops: + - :integer + OptionGroupName: + - :string + PubliclyAccessible: + - :boolean + :outputs: + :children: + CreateDBInstanceReadReplicaResult: + :ignore: true + :children: + DBInstance: + :children: + Endpoint: + :children: + Port: + :type: :integer + AllocatedStorage: + :type: :integer + InstanceCreateTime: + :type: :time + BackupRetentionPeriod: + :type: :integer + DBSecurityGroups: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_groups + :list: true + VpcSecurityGroups: + :ignore: true + :children: + VpcSecurityGroupMembership: + :rename: :vpc_security_groups + :list: true + DBParameterGroups: + :ignore: true + :children: + DBParameterGroup: + :rename: :db_parameter_groups + :list: true + DBSubnetGroup: + :children: + Subnets: + :ignore: true + :children: + Subnet: + :rename: :subnets + :list: true + :children: + SubnetAvailabilityZone: + :children: + ProvisionedIopsCapable: + :type: :boolean + PendingModifiedValues: + :children: + AllocatedStorage: + :type: :integer + Port: + :type: :integer + BackupRetentionPeriod: + :type: :integer + MultiAZ: + :type: :boolean + Iops: + :type: :integer + LatestRestorableTime: + :type: :time + MultiAZ: + :type: :boolean + AutoMinorVersionUpgrade: + :type: :boolean + ReadReplicaDBInstanceIdentifiers: + :ignore: true + :children: + ReadReplicaDBInstanceIdentifier: + :rename: :read_replica_db_instance_identifiers + :list: true + Iops: + :type: :integer + OptionGroupMemberships: + :ignore: true + :children: + OptionGroupMembership: + :rename: :option_group_memberships + :list: true + PubliclyAccessible: + :type: :boolean + :ignore: true +- :name: CreateDBParameterGroup + :method: :create_db_parameter_group + :inputs: + DBParameterGroupName: + - :string + - :required + DBParameterGroupFamily: + - :string + - :required + Description: + - :string + - :required + :outputs: + :children: + CreateDBParameterGroupResult: + :ignore: true + :children: + DBParameterGroup: + :ignore: true +- :name: CreateDBSecurityGroup + :method: :create_db_security_group + :inputs: + DBSecurityGroupName: + - :string + - :required + DBSecurityGroupDescription: + - :string + - :required + :outputs: + :children: + CreateDBSecurityGroupResult: + :ignore: true + :children: + DBSecurityGroup: + :children: + EC2SecurityGroups: + :ignore: true + :children: + EC2SecurityGroup: + :rename: :ec2_security_groups + :list: true + IPRanges: + :ignore: true + :children: + IPRange: + :rename: :ip_ranges + :list: true + :ignore: true +- :name: CreateDBSnapshot + :method: :create_db_snapshot + :inputs: + DBSnapshotIdentifier: + - :string + - :required + DBInstanceIdentifier: + - :string + - :required + :outputs: + :children: + CreateDBSnapshotResult: + :ignore: true + :children: + DBSnapshot: + :children: + SnapshotCreateTime: + :type: :time + AllocatedStorage: + :type: :integer + Port: + :type: :integer + InstanceCreateTime: + :type: :time + Iops: + :type: :integer + :ignore: true +- :name: CreateDBSubnetGroup + :method: :create_db_subnet_group + :inputs: + DBSubnetGroupName: + - :string + - :required + DBSubnetGroupDescription: + - :string + - :required + SubnetIds: + - :membered_list: + - :string + - :required + :outputs: + :children: + CreateDBSubnetGroupResult: + :ignore: true + :children: + DBSubnetGroup: + :children: + Subnets: + :ignore: true + :children: + Subnet: + :rename: :subnets + :list: true + :children: + SubnetAvailabilityZone: + :children: + ProvisionedIopsCapable: + :type: :boolean + :ignore: true +- :name: CreateEventSubscription + :method: :create_event_subscription + :inputs: + SubscriptionName: + - :string + - :required + SnsTopicArn: + - :string + - :required + SourceType: + - :string + EventCategories: + - :membered_list: + - :string + SourceIds: + - :membered_list: + - :string + Enabled: + - :boolean + :outputs: + :children: + CreateEventSubscriptionResult: + :ignore: true + :children: + EventSubscription: + :children: + SourceIdsList: + :ignore: true + :children: + SourceId: + :rename: :source_ids_list + :list: true + EventCategoriesList: + :ignore: true + :children: + EventCategory: + :rename: :event_categories_list + :list: true + Enabled: + :type: :boolean + :ignore: true +- :name: CreateOptionGroup + :method: :create_option_group + :inputs: + OptionGroupName: + - :string + - :required + EngineName: + - :string + - :required + MajorEngineVersion: + - :string + - :required + OptionGroupDescription: + - :string + - :required + :outputs: + :children: + CreateOptionGroupResult: + :ignore: true + :children: + OptionGroup: + :children: + Options: + :ignore: true + :children: + Option: + :rename: :options + :list: true + :children: + Persistent: + :type: :boolean + Port: + :type: :integer + OptionSettings: + :ignore: true + :children: + OptionSetting: + :rename: :option_settings + :list: true + :children: + IsModifiable: + :type: :boolean + IsCollection: + :type: :boolean + DBSecurityGroupMemberships: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_group_memberships + :list: true + VpcSecurityGroupMemberships: + :ignore: true + :children: + VpcSecurityGroupMembership: + :rename: :vpc_security_group_memberships + :list: true + AllowsVpcAndNonVpcInstanceMemberships: + :type: :boolean + :ignore: true +- :name: DeleteDBInstance + :method: :delete_db_instance + :inputs: + DBInstanceIdentifier: + - :string + - :required + SkipFinalSnapshot: + - :boolean + FinalDBSnapshotIdentifier: + - :string + :outputs: + :children: + DeleteDBInstanceResult: + :ignore: true + :children: + DBInstance: + :children: + Endpoint: + :children: + Port: + :type: :integer + AllocatedStorage: + :type: :integer + InstanceCreateTime: + :type: :time + BackupRetentionPeriod: + :type: :integer + DBSecurityGroups: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_groups + :list: true + VpcSecurityGroups: + :ignore: true + :children: + VpcSecurityGroupMembership: + :rename: :vpc_security_groups + :list: true + DBParameterGroups: + :ignore: true + :children: + DBParameterGroup: + :rename: :db_parameter_groups + :list: true + DBSubnetGroup: + :children: + Subnets: + :ignore: true + :children: + Subnet: + :rename: :subnets + :list: true + :children: + SubnetAvailabilityZone: + :children: + ProvisionedIopsCapable: + :type: :boolean + PendingModifiedValues: + :children: + AllocatedStorage: + :type: :integer + Port: + :type: :integer + BackupRetentionPeriod: + :type: :integer + MultiAZ: + :type: :boolean + Iops: + :type: :integer + LatestRestorableTime: + :type: :time + MultiAZ: + :type: :boolean + AutoMinorVersionUpgrade: + :type: :boolean + ReadReplicaDBInstanceIdentifiers: + :ignore: true + :children: + ReadReplicaDBInstanceIdentifier: + :rename: :read_replica_db_instance_identifiers + :list: true + Iops: + :type: :integer + OptionGroupMemberships: + :ignore: true + :children: + OptionGroupMembership: + :rename: :option_group_memberships + :list: true + PubliclyAccessible: + :type: :boolean + :ignore: true +- :name: DeleteDBParameterGroup + :method: :delete_db_parameter_group + :inputs: + DBParameterGroupName: + - :string + - :required + :outputs: + :children: + DeleteDBParameterGroupResult: + :ignore: true +- :name: DeleteDBSecurityGroup + :method: :delete_db_security_group + :inputs: + DBSecurityGroupName: + - :string + - :required + :outputs: + :children: + DeleteDBSecurityGroupResult: + :ignore: true +- :name: DeleteDBSnapshot + :method: :delete_db_snapshot + :inputs: + DBSnapshotIdentifier: + - :string + - :required + :outputs: + :children: + DeleteDBSnapshotResult: + :ignore: true + :children: + DBSnapshot: + :children: + SnapshotCreateTime: + :type: :time + AllocatedStorage: + :type: :integer + Port: + :type: :integer + InstanceCreateTime: + :type: :time + Iops: + :type: :integer + :ignore: true +- :name: DeleteDBSubnetGroup + :method: :delete_db_subnet_group + :inputs: + DBSubnetGroupName: + - :string + - :required + :outputs: + :children: + DeleteDBSubnetGroupResult: + :ignore: true +- :name: DeleteEventSubscription + :method: :delete_event_subscription + :inputs: + SubscriptionName: + - :string + - :required + :outputs: + :children: + DeleteEventSubscriptionResult: + :ignore: true + :children: + EventSubscription: + :children: + SourceIdsList: + :ignore: true + :children: + SourceId: + :rename: :source_ids_list + :list: true + EventCategoriesList: + :ignore: true + :children: + EventCategory: + :rename: :event_categories_list + :list: true + Enabled: + :type: :boolean + :ignore: true +- :name: DeleteOptionGroup + :method: :delete_option_group + :inputs: + OptionGroupName: + - :string + - :required + :outputs: + :children: + DeleteOptionGroupResult: + :ignore: true +- :name: DescribeDBEngineVersions + :method: :describe_db_engine_versions + :inputs: + Engine: + - :string + EngineVersion: + - :string + DBParameterGroupFamily: + - :string + MaxRecords: + - :integer + Marker: + - :string + DefaultOnly: + - :boolean + ListSupportedCharacterSets: + - :boolean + :outputs: + :children: + DescribeDBEngineVersionsResult: + :ignore: true + :children: + DBEngineVersions: + :ignore: true + :children: + DBEngineVersion: + :rename: :db_engine_versions + :list: true + :children: + SupportedCharacterSets: + :ignore: true + :children: + CharacterSet: + :rename: :supported_character_sets + :list: true +- :name: DescribeDBInstances + :method: :describe_db_instances + :inputs: + DBInstanceIdentifier: + - :string + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeDBInstancesResult: + :ignore: true + :children: + DBInstances: + :ignore: true + :children: + DBInstance: + :rename: :db_instances + :list: true + :children: + Endpoint: + :children: + Port: + :type: :integer + AllocatedStorage: + :type: :integer + InstanceCreateTime: + :type: :time + BackupRetentionPeriod: + :type: :integer + DBSecurityGroups: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_groups + :list: true + VpcSecurityGroups: + :ignore: true + :children: + VpcSecurityGroupMembership: + :rename: :vpc_security_groups + :list: true + DBParameterGroups: + :ignore: true + :children: + DBParameterGroup: + :rename: :db_parameter_groups + :list: true + DBSubnetGroup: + :children: + Subnets: + :ignore: true + :children: + Subnet: + :rename: :subnets + :list: true + :children: + SubnetAvailabilityZone: + :children: + ProvisionedIopsCapable: + :type: :boolean + PendingModifiedValues: + :children: + AllocatedStorage: + :type: :integer + Port: + :type: :integer + BackupRetentionPeriod: + :type: :integer + MultiAZ: + :type: :boolean + Iops: + :type: :integer + LatestRestorableTime: + :type: :time + MultiAZ: + :type: :boolean + AutoMinorVersionUpgrade: + :type: :boolean + ReadReplicaDBInstanceIdentifiers: + :ignore: true + :children: + ReadReplicaDBInstanceIdentifier: + :rename: :read_replica_db_instance_identifiers + :list: true + Iops: + :type: :integer + OptionGroupMemberships: + :ignore: true + :children: + OptionGroupMembership: + :rename: :option_group_memberships + :list: true + PubliclyAccessible: + :type: :boolean +- :name: DescribeDBLogFiles + :method: :describe_db_log_files + :inputs: + DBInstanceIdentifier: + - :string + FilenameContains: + - :string + FileLastWritten: + - :long + FileSize: + - :long + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeDBLogFilesResult: + :ignore: true + :children: + DescribeDBLogFiles: + :ignore: true + :children: + DescribeDBLogFilesDetails: + :rename: :describe_db_log_files + :list: true + :children: + LastWritten: + :type: :integer + Size: + :type: :integer +- :name: DescribeDBParameterGroups + :method: :describe_db_parameter_groups + :inputs: + DBParameterGroupName: + - :string + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeDBParameterGroupsResult: + :ignore: true + :children: + DBParameterGroups: + :ignore: true + :children: + DBParameterGroup: + :rename: :db_parameter_groups + :list: true +- :name: DescribeDBParameters + :method: :describe_db_parameters + :inputs: + DBParameterGroupName: + - :string + - :required + Source: + - :string + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeDBParametersResult: + :ignore: true + :children: + Parameters: + :ignore: true + :children: + Parameter: + :rename: :parameters + :list: true + :children: + IsModifiable: + :type: :boolean +- :name: DescribeDBSecurityGroups + :method: :describe_db_security_groups + :inputs: + DBSecurityGroupName: + - :string + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeDBSecurityGroupsResult: + :ignore: true + :children: + DBSecurityGroups: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_groups + :list: true + :children: + EC2SecurityGroups: + :ignore: true + :children: + EC2SecurityGroup: + :rename: :ec2_security_groups + :list: true + IPRanges: + :ignore: true + :children: + IPRange: + :rename: :ip_ranges + :list: true +- :name: DescribeDBSnapshots + :method: :describe_db_snapshots + :inputs: + DBInstanceIdentifier: + - :string + DBSnapshotIdentifier: + - :string + SnapshotType: + - :string + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeDBSnapshotsResult: + :ignore: true + :children: + DBSnapshots: + :ignore: true + :children: + DBSnapshot: + :rename: :db_snapshots + :list: true + :children: + SnapshotCreateTime: + :type: :time + AllocatedStorage: + :type: :integer + Port: + :type: :integer + InstanceCreateTime: + :type: :time + Iops: + :type: :integer +- :name: DescribeDBSubnetGroups + :method: :describe_db_subnet_groups + :inputs: + DBSubnetGroupName: + - :string + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeDBSubnetGroupsResult: + :ignore: true + :children: + DBSubnetGroups: + :ignore: true + :children: + DBSubnetGroup: + :rename: :db_subnet_groups + :list: true + :children: + Subnets: + :ignore: true + :children: + Subnet: + :rename: :subnets + :list: true + :children: + SubnetAvailabilityZone: + :children: + ProvisionedIopsCapable: + :type: :boolean +- :name: DescribeEngineDefaultParameters + :method: :describe_engine_default_parameters + :inputs: + DBParameterGroupFamily: + - :string + - :required + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeEngineDefaultParametersResult: + :ignore: true + :children: + EngineDefaults: + :children: + Parameters: + :ignore: true + :children: + Parameter: + :rename: :parameters + :list: true + :children: + IsModifiable: + :type: :boolean + :ignore: true +- :name: DescribeEventCategories + :method: :describe_event_categories + :inputs: + SourceType: + - :string + :outputs: + :children: + DescribeEventCategoriesResult: + :ignore: true + :children: + EventCategoriesMapList: + :ignore: true + :children: + EventCategoriesMap: + :rename: :event_categories_map_list + :list: true + :children: + EventCategories: + :ignore: true + :children: + EventCategory: + :rename: :event_categories + :list: true +- :name: DescribeEventSubscriptions + :method: :describe_event_subscriptions + :inputs: + SubscriptionName: + - :string + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeEventSubscriptionsResult: + :ignore: true + :children: + EventSubscriptionsList: + :ignore: true + :children: + EventSubscription: + :rename: :event_subscriptions_list + :list: true + :children: + SourceIdsList: + :ignore: true + :children: + SourceId: + :rename: :source_ids_list + :list: true + EventCategoriesList: + :ignore: true + :children: + EventCategory: + :rename: :event_categories_list + :list: true + Enabled: + :type: :boolean +- :name: DescribeEvents + :method: :describe_events + :inputs: + SourceIdentifier: + - :string + SourceType: + - :string + StartTime: + - :timestamp + EndTime: + - :timestamp + Duration: + - :integer + EventCategories: + - :membered_list: + - :string + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeEventsResult: + :ignore: true + :children: + Events: + :ignore: true + :children: + Event: + :rename: :events + :list: true + :children: + EventCategories: + :ignore: true + :children: + EventCategory: + :rename: :event_categories + :list: true + Date: + :type: :time +- :name: DescribeOptionGroupOptions + :method: :describe_option_group_options + :inputs: + EngineName: + - :string + - :required + MajorEngineVersion: + - :string + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeOptionGroupOptionsResult: + :ignore: true + :children: + OptionGroupOptions: + :ignore: true + :children: + OptionGroupOption: + :rename: :option_group_options + :list: true + :children: + PortRequired: + :type: :boolean + DefaultPort: + :type: :integer + OptionsDependedOn: + :ignore: true + :children: + OptionName: + :rename: :options_depended_on + :list: true + Persistent: + :type: :boolean + OptionGroupOptionSettings: + :ignore: true + :children: + OptionGroupOptionSetting: + :rename: :option_group_option_settings + :list: true + :children: + IsModifiable: + :type: :boolean +- :name: DescribeOptionGroups + :method: :describe_option_groups + :inputs: + OptionGroupName: + - :string + Marker: + - :string + MaxRecords: + - :integer + EngineName: + - :string + MajorEngineVersion: + - :string + :outputs: + :children: + DescribeOptionGroupsResult: + :ignore: true + :children: + OptionGroupsList: + :ignore: true + :children: + OptionGroup: + :rename: :option_groups_list + :list: true + :children: + Options: + :ignore: true + :children: + Option: + :rename: :options + :list: true + :children: + Persistent: + :type: :boolean + Port: + :type: :integer + OptionSettings: + :ignore: true + :children: + OptionSetting: + :rename: :option_settings + :list: true + :children: + IsModifiable: + :type: :boolean + IsCollection: + :type: :boolean + DBSecurityGroupMemberships: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_group_memberships + :list: true + VpcSecurityGroupMemberships: + :ignore: true + :children: + VpcSecurityGroupMembership: + :rename: :vpc_security_group_memberships + :list: true + AllowsVpcAndNonVpcInstanceMemberships: + :type: :boolean +- :name: DescribeOrderableDBInstanceOptions + :method: :describe_orderable_db_instance_options + :inputs: + Engine: + - :string + - :required + EngineVersion: + - :string + DBInstanceClass: + - :string + LicenseModel: + - :string + Vpc: + - :boolean + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeOrderableDBInstanceOptionsResult: + :ignore: true + :children: + OrderableDBInstanceOptions: + :ignore: true + :children: + OrderableDBInstanceOption: + :rename: :orderable_db_instance_options + :list: true + :children: + AvailabilityZones: + :ignore: true + :children: + AvailabilityZone: + :rename: :availability_zones + :list: true + :children: + ProvisionedIopsCapable: + :type: :boolean + MultiAZCapable: + :type: :boolean + ReadReplicaCapable: + :type: :boolean + Vpc: + :type: :boolean +- :name: DescribeReservedDBInstances + :method: :describe_reserved_db_instances + :inputs: + ReservedDBInstanceId: + - :string + ReservedDBInstancesOfferingId: + - :string + DBInstanceClass: + - :string + Duration: + - :string + ProductDescription: + - :string + OfferingType: + - :string + MultiAZ: + - :boolean + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeReservedDBInstancesResult: + :ignore: true + :children: + ReservedDBInstances: + :ignore: true + :children: + ReservedDBInstance: + :rename: :reserved_db_instances + :list: true + :children: + StartTime: + :type: :time + Duration: + :type: :integer + FixedPrice: + :type: :float + UsagePrice: + :type: :float + DBInstanceCount: + :type: :integer + MultiAZ: + :type: :boolean + RecurringCharges: + :ignore: true + :children: + RecurringCharge: + :rename: :recurring_charges + :list: true + :children: + RecurringChargeAmount: + :type: :float +- :name: DescribeReservedDBInstancesOfferings + :method: :describe_reserved_db_instances_offerings + :inputs: + ReservedDBInstancesOfferingId: + - :string + DBInstanceClass: + - :string + Duration: + - :string + ProductDescription: + - :string + OfferingType: + - :string + MultiAZ: + - :boolean + MaxRecords: + - :integer + Marker: + - :string + :outputs: + :children: + DescribeReservedDBInstancesOfferingsResult: + :ignore: true + :children: + ReservedDBInstancesOfferings: + :ignore: true + :children: + ReservedDBInstancesOffering: + :rename: :reserved_db_instances_offerings + :list: true + :children: + Duration: + :type: :integer + FixedPrice: + :type: :float + UsagePrice: + :type: :float + MultiAZ: + :type: :boolean + RecurringCharges: + :ignore: true + :children: + RecurringCharge: + :rename: :recurring_charges + :list: true + :children: + RecurringChargeAmount: + :type: :float +- :name: DownloadDBLogFilePortion + :method: :download_db_log_file_portion + :inputs: + DBInstanceIdentifier: + - :string + LogFileName: + - :string + Marker: + - :string + NumberOfLines: + - :integer + :outputs: + :children: + DownloadDBLogFilePortionResult: + :ignore: true + :children: + AdditionalDataPending: + :type: :boolean +- :name: ListTagsForResource + :method: :list_tags_for_resource + :inputs: + ResourceName: + - :string + - :required + :outputs: + :children: + ListTagsForResourceResult: + :ignore: true + :children: + TagList: + :ignore: true + :children: + Tag: + :rename: :tag_list + :list: true +- :name: ModifyDBInstance + :method: :modify_db_instance + :inputs: + DBInstanceIdentifier: + - :string + - :required + AllocatedStorage: + - :integer + DBInstanceClass: + - :string + DBSecurityGroups: + - :membered_list: + - :string + VpcSecurityGroupIds: + - :membered_list: + - :string + ApplyImmediately: + - :boolean + MasterUserPassword: + - :string + DBParameterGroupName: + - :string + BackupRetentionPeriod: + - :integer + PreferredBackupWindow: + - :string + PreferredMaintenanceWindow: + - :string + MultiAZ: + - :boolean + EngineVersion: + - :string + AllowMajorVersionUpgrade: + - :boolean + AutoMinorVersionUpgrade: + - :boolean + Iops: + - :integer + OptionGroupName: + - :string + NewDBInstanceIdentifier: + - :string + :outputs: + :children: + ModifyDBInstanceResult: + :ignore: true + :children: + DBInstance: + :children: + Endpoint: + :children: + Port: + :type: :integer + AllocatedStorage: + :type: :integer + InstanceCreateTime: + :type: :time + BackupRetentionPeriod: + :type: :integer + DBSecurityGroups: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_groups + :list: true + VpcSecurityGroups: + :ignore: true + :children: + VpcSecurityGroupMembership: + :rename: :vpc_security_groups + :list: true + DBParameterGroups: + :ignore: true + :children: + DBParameterGroup: + :rename: :db_parameter_groups + :list: true + DBSubnetGroup: + :children: + Subnets: + :ignore: true + :children: + Subnet: + :rename: :subnets + :list: true + :children: + SubnetAvailabilityZone: + :children: + ProvisionedIopsCapable: + :type: :boolean + PendingModifiedValues: + :children: + AllocatedStorage: + :type: :integer + Port: + :type: :integer + BackupRetentionPeriod: + :type: :integer + MultiAZ: + :type: :boolean + Iops: + :type: :integer + LatestRestorableTime: + :type: :time + MultiAZ: + :type: :boolean + AutoMinorVersionUpgrade: + :type: :boolean + ReadReplicaDBInstanceIdentifiers: + :ignore: true + :children: + ReadReplicaDBInstanceIdentifier: + :rename: :read_replica_db_instance_identifiers + :list: true + Iops: + :type: :integer + OptionGroupMemberships: + :ignore: true + :children: + OptionGroupMembership: + :rename: :option_group_memberships + :list: true + PubliclyAccessible: + :type: :boolean + :ignore: true +- :name: ModifyDBParameterGroup + :method: :modify_db_parameter_group + :inputs: + DBParameterGroupName: + - :string + - :required + Parameters: + - :membered_list: + - :structure: + ParameterName: + - :string + ParameterValue: + - :string + Description: + - :string + Source: + - :string + ApplyType: + - :string + DataType: + - :string + AllowedValues: + - :string + IsModifiable: + - :boolean + MinimumEngineVersion: + - :string + ApplyMethod: + - :string + - :required + :outputs: + :children: + ModifyDBParameterGroupResult: + :ignore: true +- :name: ModifyDBSubnetGroup + :method: :modify_db_subnet_group + :inputs: + DBSubnetGroupName: + - :string + - :required + DBSubnetGroupDescription: + - :string + SubnetIds: + - :membered_list: + - :string + - :required + :outputs: + :children: + ModifyDBSubnetGroupResult: + :ignore: true + :children: + DBSubnetGroup: + :children: + Subnets: + :ignore: true + :children: + Subnet: + :rename: :subnets + :list: true + :children: + SubnetAvailabilityZone: + :children: + ProvisionedIopsCapable: + :type: :boolean + :ignore: true +- :name: ModifyEventSubscription + :method: :modify_event_subscription + :inputs: + SubscriptionName: + - :string + - :required + SnsTopicArn: + - :string + SourceType: + - :string + EventCategories: + - :membered_list: + - :string + Enabled: + - :boolean + :outputs: + :children: + ModifyEventSubscriptionResult: + :ignore: true + :children: + EventSubscription: + :children: + SourceIdsList: + :ignore: true + :children: + SourceId: + :rename: :source_ids_list + :list: true + EventCategoriesList: + :ignore: true + :children: + EventCategory: + :rename: :event_categories_list + :list: true + Enabled: + :type: :boolean + :ignore: true +- :name: ModifyOptionGroup + :method: :modify_option_group + :inputs: + OptionGroupName: + - :string + - :required + OptionsToInclude: + - :membered_list: + - :structure: + OptionName: + - :string + - :required + Port: + - :integer + DBSecurityGroupMemberships: + - :membered_list: + - :string + VpcSecurityGroupMemberships: + - :membered_list: + - :string + OptionSettings: + - :membered_list: + - :structure: + Name: + - :string + Value: + - :string + DefaultValue: + - :string + Description: + - :string + ApplyType: + - :string + DataType: + - :string + AllowedValues: + - :string + IsModifiable: + - :boolean + IsCollection: + - :boolean + OptionsToRemove: + - :membered_list: + - :string + ApplyImmediately: + - :boolean + :outputs: + :children: + ModifyOptionGroupResult: + :ignore: true + :children: + OptionGroup: + :children: + Options: + :ignore: true + :children: + Option: + :rename: :options + :list: true + :children: + Persistent: + :type: :boolean + Port: + :type: :integer + OptionSettings: + :ignore: true + :children: + OptionSetting: + :rename: :option_settings + :list: true + :children: + IsModifiable: + :type: :boolean + IsCollection: + :type: :boolean + DBSecurityGroupMemberships: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_group_memberships + :list: true + VpcSecurityGroupMemberships: + :ignore: true + :children: + VpcSecurityGroupMembership: + :rename: :vpc_security_group_memberships + :list: true + AllowsVpcAndNonVpcInstanceMemberships: + :type: :boolean + :ignore: true +- :name: PromoteReadReplica + :method: :promote_read_replica + :inputs: + DBInstanceIdentifier: + - :string + - :required + BackupRetentionPeriod: + - :integer + PreferredBackupWindow: + - :string + :outputs: + :children: + PromoteReadReplicaResult: + :ignore: true + :children: + DBInstance: + :children: + Endpoint: + :children: + Port: + :type: :integer + AllocatedStorage: + :type: :integer + InstanceCreateTime: + :type: :time + BackupRetentionPeriod: + :type: :integer + DBSecurityGroups: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_groups + :list: true + VpcSecurityGroups: + :ignore: true + :children: + VpcSecurityGroupMembership: + :rename: :vpc_security_groups + :list: true + DBParameterGroups: + :ignore: true + :children: + DBParameterGroup: + :rename: :db_parameter_groups + :list: true + DBSubnetGroup: + :children: + Subnets: + :ignore: true + :children: + Subnet: + :rename: :subnets + :list: true + :children: + SubnetAvailabilityZone: + :children: + ProvisionedIopsCapable: + :type: :boolean + PendingModifiedValues: + :children: + AllocatedStorage: + :type: :integer + Port: + :type: :integer + BackupRetentionPeriod: + :type: :integer + MultiAZ: + :type: :boolean + Iops: + :type: :integer + LatestRestorableTime: + :type: :time + MultiAZ: + :type: :boolean + AutoMinorVersionUpgrade: + :type: :boolean + ReadReplicaDBInstanceIdentifiers: + :ignore: true + :children: + ReadReplicaDBInstanceIdentifier: + :rename: :read_replica_db_instance_identifiers + :list: true + Iops: + :type: :integer + OptionGroupMemberships: + :ignore: true + :children: + OptionGroupMembership: + :rename: :option_group_memberships + :list: true + PubliclyAccessible: + :type: :boolean + :ignore: true +- :name: PurchaseReservedDBInstancesOffering + :method: :purchase_reserved_db_instances_offering + :inputs: + ReservedDBInstancesOfferingId: + - :string + - :required + ReservedDBInstanceId: + - :string + DBInstanceCount: + - :integer + :outputs: + :children: + PurchaseReservedDBInstancesOfferingResult: + :ignore: true + :children: + ReservedDBInstance: + :children: + StartTime: + :type: :time + Duration: + :type: :integer + FixedPrice: + :type: :float + UsagePrice: + :type: :float + DBInstanceCount: + :type: :integer + MultiAZ: + :type: :boolean + RecurringCharges: + :ignore: true + :children: + RecurringCharge: + :rename: :recurring_charges + :list: true + :children: + RecurringChargeAmount: + :type: :float + :ignore: true +- :name: RebootDBInstance + :method: :reboot_db_instance + :inputs: + DBInstanceIdentifier: + - :string + - :required + ForceFailover: + - :boolean + :outputs: + :children: + RebootDBInstanceResult: + :ignore: true + :children: + DBInstance: + :children: + Endpoint: + :children: + Port: + :type: :integer + AllocatedStorage: + :type: :integer + InstanceCreateTime: + :type: :time + BackupRetentionPeriod: + :type: :integer + DBSecurityGroups: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_groups + :list: true + VpcSecurityGroups: + :ignore: true + :children: + VpcSecurityGroupMembership: + :rename: :vpc_security_groups + :list: true + DBParameterGroups: + :ignore: true + :children: + DBParameterGroup: + :rename: :db_parameter_groups + :list: true + DBSubnetGroup: + :children: + Subnets: + :ignore: true + :children: + Subnet: + :rename: :subnets + :list: true + :children: + SubnetAvailabilityZone: + :children: + ProvisionedIopsCapable: + :type: :boolean + PendingModifiedValues: + :children: + AllocatedStorage: + :type: :integer + Port: + :type: :integer + BackupRetentionPeriod: + :type: :integer + MultiAZ: + :type: :boolean + Iops: + :type: :integer + LatestRestorableTime: + :type: :time + MultiAZ: + :type: :boolean + AutoMinorVersionUpgrade: + :type: :boolean + ReadReplicaDBInstanceIdentifiers: + :ignore: true + :children: + ReadReplicaDBInstanceIdentifier: + :rename: :read_replica_db_instance_identifiers + :list: true + Iops: + :type: :integer + OptionGroupMemberships: + :ignore: true + :children: + OptionGroupMembership: + :rename: :option_group_memberships + :list: true + PubliclyAccessible: + :type: :boolean + :ignore: true +- :name: RemoveSourceIdentifierFromSubscription + :method: :remove_source_identifier_from_subscription + :inputs: + SubscriptionName: + - :string + - :required + SourceIdentifier: + - :string + - :required + :outputs: + :children: + RemoveSourceIdentifierFromSubscriptionResult: + :ignore: true + :children: + EventSubscription: + :children: + SourceIdsList: + :ignore: true + :children: + SourceId: + :rename: :source_ids_list + :list: true + EventCategoriesList: + :ignore: true + :children: + EventCategory: + :rename: :event_categories_list + :list: true + Enabled: + :type: :boolean + :ignore: true +- :name: RemoveTagsFromResource + :method: :remove_tags_from_resource + :inputs: + ResourceName: + - :string + - :required + TagKeys: + - :membered_list: + - :string + - :required + :outputs: + :children: + RemoveTagsFromResourceResult: + :ignore: true +- :name: ResetDBParameterGroup + :method: :reset_db_parameter_group + :inputs: + DBParameterGroupName: + - :string + - :required + ResetAllParameters: + - :boolean + Parameters: + - :membered_list: + - :structure: + ParameterName: + - :string + ParameterValue: + - :string + Description: + - :string + Source: + - :string + ApplyType: + - :string + DataType: + - :string + AllowedValues: + - :string + IsModifiable: + - :boolean + MinimumEngineVersion: + - :string + ApplyMethod: + - :string + :outputs: + :children: + ResetDBParameterGroupResult: + :ignore: true +- :name: RestoreDBInstanceFromDBSnapshot + :method: :restore_db_instance_from_db_snapshot + :inputs: + DBInstanceIdentifier: + - :string + - :required + DBSnapshotIdentifier: + - :string + - :required + DBInstanceClass: + - :string + Port: + - :integer + AvailabilityZone: + - :string + DBSubnetGroupName: + - :string + MultiAZ: + - :boolean + PubliclyAccessible: + - :boolean + AutoMinorVersionUpgrade: + - :boolean + LicenseModel: + - :string + DBName: + - :string + Engine: + - :string + Iops: + - :integer + OptionGroupName: + - :string + :outputs: + :children: + RestoreDBInstanceFromDBSnapshotResult: + :ignore: true + :children: + DBInstance: + :children: + Endpoint: + :children: + Port: + :type: :integer + AllocatedStorage: + :type: :integer + InstanceCreateTime: + :type: :time + BackupRetentionPeriod: + :type: :integer + DBSecurityGroups: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_groups + :list: true + VpcSecurityGroups: + :ignore: true + :children: + VpcSecurityGroupMembership: + :rename: :vpc_security_groups + :list: true + DBParameterGroups: + :ignore: true + :children: + DBParameterGroup: + :rename: :db_parameter_groups + :list: true + DBSubnetGroup: + :children: + Subnets: + :ignore: true + :children: + Subnet: + :rename: :subnets + :list: true + :children: + SubnetAvailabilityZone: + :children: + ProvisionedIopsCapable: + :type: :boolean + PendingModifiedValues: + :children: + AllocatedStorage: + :type: :integer + Port: + :type: :integer + BackupRetentionPeriod: + :type: :integer + MultiAZ: + :type: :boolean + Iops: + :type: :integer + LatestRestorableTime: + :type: :time + MultiAZ: + :type: :boolean + AutoMinorVersionUpgrade: + :type: :boolean + ReadReplicaDBInstanceIdentifiers: + :ignore: true + :children: + ReadReplicaDBInstanceIdentifier: + :rename: :read_replica_db_instance_identifiers + :list: true + Iops: + :type: :integer + OptionGroupMemberships: + :ignore: true + :children: + OptionGroupMembership: + :rename: :option_group_memberships + :list: true + PubliclyAccessible: + :type: :boolean + :ignore: true +- :name: RestoreDBInstanceToPointInTime + :method: :restore_db_instance_to_point_in_time + :inputs: + SourceDBInstanceIdentifier: + - :string + - :required + TargetDBInstanceIdentifier: + - :string + - :required + RestoreTime: + - :timestamp + UseLatestRestorableTime: + - :boolean + DBInstanceClass: + - :string + Port: + - :integer + AvailabilityZone: + - :string + DBSubnetGroupName: + - :string + MultiAZ: + - :boolean + PubliclyAccessible: + - :boolean + AutoMinorVersionUpgrade: + - :boolean + LicenseModel: + - :string + DBName: + - :string + Engine: + - :string + Iops: + - :integer + OptionGroupName: + - :string + :outputs: + :children: + RestoreDBInstanceToPointInTimeResult: + :ignore: true + :children: + DBInstance: + :children: + Endpoint: + :children: + Port: + :type: :integer + AllocatedStorage: + :type: :integer + InstanceCreateTime: + :type: :time + BackupRetentionPeriod: + :type: :integer + DBSecurityGroups: + :ignore: true + :children: + DBSecurityGroup: + :rename: :db_security_groups + :list: true + VpcSecurityGroups: + :ignore: true + :children: + VpcSecurityGroupMembership: + :rename: :vpc_security_groups + :list: true + DBParameterGroups: + :ignore: true + :children: + DBParameterGroup: + :rename: :db_parameter_groups + :list: true + DBSubnetGroup: + :children: + Subnets: + :ignore: true + :children: + Subnet: + :rename: :subnets + :list: true + :children: + SubnetAvailabilityZone: + :children: + ProvisionedIopsCapable: + :type: :boolean + PendingModifiedValues: + :children: + AllocatedStorage: + :type: :integer + Port: + :type: :integer + BackupRetentionPeriod: + :type: :integer + MultiAZ: + :type: :boolean + Iops: + :type: :integer + LatestRestorableTime: + :type: :time + MultiAZ: + :type: :boolean + AutoMinorVersionUpgrade: + :type: :boolean + ReadReplicaDBInstanceIdentifiers: + :ignore: true + :children: + ReadReplicaDBInstanceIdentifier: + :rename: :read_replica_db_instance_identifiers + :list: true + Iops: + :type: :integer + OptionGroupMemberships: + :ignore: true + :children: + OptionGroupMembership: + :rename: :option_group_memberships + :list: true + PubliclyAccessible: + :type: :boolean + :ignore: true +- :name: RevokeDBSecurityGroupIngress + :method: :revoke_db_security_group_ingress + :inputs: + DBSecurityGroupName: + - :string + - :required + CIDRIP: + - :string + EC2SecurityGroupName: + - :string + EC2SecurityGroupId: + - :string + EC2SecurityGroupOwnerId: + - :string + :outputs: + :children: + RevokeDBSecurityGroupIngressResult: + :ignore: true + :children: + DBSecurityGroup: + :children: + EC2SecurityGroups: + :ignore: true + :children: + EC2SecurityGroup: + :rename: :ec2_security_groups + :list: true + IPRanges: + :ignore: true + :children: + IPRange: + :rename: :ip_ranges + :list: true + :ignore: true diff --git a/lib/aws/rds/client.rb b/lib/aws/rds/client.rb index b43d316adf6..04c5eb823e5 100644 --- a/lib/aws/rds/client.rb +++ b/lib/aws/rds/client.rb @@ -22,6 +22,35 @@ class Client < Core::QueryClient # client methods # + # @!method add_source_identifier_to_subscription(options = {}) + # Calls the AddSourceIdentifierToSubscription API operation. + # @param [Hash] options + # * +:subscription_name+ - *required* - (String) The name of the RDS + # event notification subscription you want to add a source identifier + # to. + # * +:source_identifier+ - *required* - (String) The identifier of the + # event source to be added. An identifier must begin with a letter + # and must contain only ASCII letters, digits, and hyphens; it cannot + # end with a hyphen or contain two consecutive hyphens. Constraints: + # If the source type is a DB instance, then a DBInstanceIdentifier + # must be supplied. If the source type is a DB security group, a + # DBSecurityGroupName must be supplied. If the source type is a DB + # parameter group, a DBParameterGroupName must be supplied. If the + # source type is a DB Snapshot, a DBSnapshotIdentifier must be + # supplied. + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:customer_aws_id+ - (String) + # * +:cust_subscription_id+ - (String) + # * +:sns_topic_arn+ - (String) + # * +:status+ - (String) + # * +:subscription_creation_time+ - (String) + # * +:source_type+ - (String) + # * +:source_ids_list+ - (Array) + # * +:event_categories_list+ - (Array) + # * +:enabled+ - (Boolean) + # @!method add_tags_to_resource(options = {}) # Calls the AddTagsToResource API operation. # @param [Hash] options @@ -104,6 +133,7 @@ class Client < Core::QueryClient # * +:license_model+ - (String) # * +:snapshot_type+ - (String) # * +:iops+ - (Integer) + # * +:option_group_name+ - (String) # @!method create_db_instance(options = {}) # Calls the CreateDBInstance API operation. @@ -125,6 +155,9 @@ class Client < Core::QueryClient # the master DB Instance user. # * +:db_security_groups+ - (Array) A list of DB Security # Groups to associate with this DB Instance. + # * +:vpc_security_group_ids+ - (Array) A list of Ec2 Vpc + # Security Groups to associate with this DB Instance. Default: The + # default Ec2 Vpc Security Group for the DB Subnet group's Vpc. # * +:availability_zone+ - (String) The EC2 Availability Zone that the # database instance will be created in. # * +:db_subnet_group_name+ - (String) A DB Subnet Group to associate @@ -164,6 +197,19 @@ class Client < Core::QueryClient # * +:character_set_name+ - (String) For supported engines, indicates # that the DB Instance should be associated with the specified # CharacterSet. + # * +:publicly_accessible+ - (Boolean) Specifies the accessibility + # options for the DB Instance. A value of +true+ specifies an + # Internet-facing instance with a publicly resolvable DNS name, which + # resolves to a public IP address. A value of +false+ specifies an + # internal instance with a DNS name that resolves to a private IP + # address. Default: The default behavior varies depending on whether + # a VPC has been requested or not. The following list shows the + # default behavior in each case. Default VPC: +true+ VPC: +false+ If + # no DB subnet group has been specified as part of the request and + # the PubliclyAccessible value has not been set, the DB instance will + # be publicly accessible. If a specific DB subnet group has been + # specified as part of the request and the PubliclyAccessible value + # has not been set, the DB instance will be private. # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -183,6 +229,9 @@ class Client < Core::QueryClient # * +:db_security_groups+ - (Array) # * +:db_security_group_name+ - (String) # * +:status+ - (String) + # * +:vpc_security_groups+ - (Array) + # * +:vpc_security_group_id+ - (String) + # * +:status+ - (String) # * +:db_parameter_groups+ - (Array) # * +:db_parameter_group_name+ - (String) # * +:parameter_apply_status+ - (String) @@ -208,6 +257,7 @@ class Client < Core::QueryClient # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) # * +:iops+ - (Integer) + # * +:db_instance_identifier+ - (String) # * +:latest_restorable_time+ - (Time) # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) @@ -216,10 +266,12 @@ class Client < Core::QueryClient # * +:read_replica_db_instance_identifiers+ - (Array) # * +:license_model+ - (String) # * +:iops+ - (Integer) - # * +:option_group_membership+ - (Hash) + # * +:option_group_memberships+ - (Array) # * +:option_group_name+ - (String) # * +:status+ - (String) # * +:character_set_name+ - (String) + # * +:secondary_availability_zone+ - (String) + # * +:publicly_accessible+ - (Boolean) # @!method create_db_instance_read_replica(options = {}) # Calls the CreateDBInstanceReadReplica API operation. @@ -251,7 +303,22 @@ class Client < Core::QueryClient # * +:iops+ - (Integer) The amount of Provisioned IOPS (input/output # operations per second) to be initially allocated for the DB # Instance. - # * +:option_group_name+ - (String) + # * +:option_group_name+ - (String) The option group the DB instance + # will be associated with. If omitted, the default Option Group for + # the engine specified will be used. + # * +:publicly_accessible+ - (Boolean) Specifies the accessibility + # options for the DB Instance. A value of +true+ specifies an + # Internet-facing instance with a publicly resolvable DNS name, which + # resolves to a public IP address. A value of +false+ specifies an + # internal instance with a DNS name that resolves to a private IP + # address. Default: The default behavior varies depending on whether + # a VPC has been requested or not. The following list shows the + # default behavior in each case. Default VPC: +true+ VPC: +false+ If + # no DB subnet group has been specified as part of the request and + # the PubliclyAccessible value has not been set, the DB instance will + # be publicly accessible. If a specific DB subnet group has been + # specified as part of the request and the PubliclyAccessible value + # has not been set, the DB instance will be private. # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -271,6 +338,9 @@ class Client < Core::QueryClient # * +:db_security_groups+ - (Array) # * +:db_security_group_name+ - (String) # * +:status+ - (String) + # * +:vpc_security_groups+ - (Array) + # * +:vpc_security_group_id+ - (String) + # * +:status+ - (String) # * +:db_parameter_groups+ - (Array) # * +:db_parameter_group_name+ - (String) # * +:parameter_apply_status+ - (String) @@ -296,6 +366,7 @@ class Client < Core::QueryClient # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) # * +:iops+ - (Integer) + # * +:db_instance_identifier+ - (String) # * +:latest_restorable_time+ - (Time) # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) @@ -304,10 +375,12 @@ class Client < Core::QueryClient # * +:read_replica_db_instance_identifiers+ - (Array) # * +:license_model+ - (String) # * +:iops+ - (Integer) - # * +:option_group_membership+ - (Hash) + # * +:option_group_memberships+ - (Array) # * +:option_group_name+ - (String) # * +:status+ - (String) # * +:character_set_name+ - (String) + # * +:secondary_availability_zone+ - (String) + # * +:publicly_accessible+ - (Boolean) # @!method create_db_parameter_group(options = {}) # Calls the CreateDBParameterGroup API operation. @@ -333,9 +406,6 @@ class Client < Core::QueryClient # DB Security Group. This value is stored as a lowercase string. # * +:db_security_group_description+ - *required* - (String) The # description for the DB Security Group. - # * +:ec2_vpc_id+ - (String) The Id of VPC. Indicates which VPC this DB - # Security Group should belong to. Must be specified to create a DB - # Security Group for a VPC; may not be specified otherwise. # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -378,6 +448,7 @@ class Client < Core::QueryClient # * +:license_model+ - (String) # * +:snapshot_type+ - (String) # * +:iops+ - (Integer) + # * +:option_group_name+ - (String) # @!method create_db_subnet_group(options = {}) # Calls the CreateDBSubnetGroup API operation. @@ -404,6 +475,54 @@ class Client < Core::QueryClient # * +:provisioned_iops_capable+ - (Boolean) # * +:subnet_status+ - (String) + # @!method create_event_subscription(options = {}) + # Calls the CreateEventSubscription API operation. + # @param [Hash] options + # * +:subscription_name+ - *required* - (String) The name of the + # subscription. Constraints: The name must be less than 255 + # characters. + # * +:sns_topic_arn+ - *required* - (String) The Amazon Resource Name + # (ARN) of the SNS topic created for event notification. The ARN is + # created by Amazon SNS when you create a topic and subscribe to it. + # * +:source_type+ - (String) The type of source that will be + # generating the events. For example, if you want to be notified of + # events generated by a DB instance, you would set this parameter to + # db-instance. if this value is not specified, all events are + # returned. Valid values: db-instance | db-parameter-group | + # db-security-group | db-snapshot + # * +:event_categories+ - (Array) A list of event categories + # for a SourceType that you want to subscribe to. You can see a list + # of the categories for a given SourceType in the Events topic in the + # Amazon RDS User Guide or by using the DescribeEventCategories + # action. + # * +:source_ids+ - (Array) The list of identifiers of the + # event sources for which events will be returned. If not specified, + # then all sources are included in the response. An identifier must + # begin with a letter and must contain only ASCII letters, digits, + # and hyphens; it cannot end with a hyphen or contain two consecutive + # hyphens. Constraints: If SourceIds are supplied, SourceType must + # also be provided. If the source type is a DB instance, then a + # DBInstanceIdentifier must be supplied. If the source type is a DB + # security group, a DBSecurityGroupName must be supplied. If the + # source type is a DB parameter group, a DBParameterGroupName must be + # supplied. If the source type is a DB Snapshot, a + # DBSnapshotIdentifier must be supplied. + # * +:enabled+ - (Boolean) A Boolean value; set to +true+ to activate + # the subscription, set to +false+ to create the subscription but not + # active it. + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:customer_aws_id+ - (String) + # * +:cust_subscription_id+ - (String) + # * +:sns_topic_arn+ - (String) + # * +:status+ - (String) + # * +:subscription_creation_time+ - (String) + # * +:source_type+ - (String) + # * +:source_ids_list+ - (Array) + # * +:event_categories_list+ - (Array) + # * +:enabled+ - (Boolean) + # @!method create_option_group(options = {}) # Calls the CreateOptionGroup API operation. # @param [Hash] options @@ -429,10 +548,24 @@ class Client < Core::QueryClient # * +:options+ - (Array) # * +:option_name+ - (String) # * +:option_description+ - (String) + # * +:persistent+ - (Boolean) # * +:port+ - (Integer) + # * +:option_settings+ - (Array) + # * +:name+ - (String) + # * +:value+ - (String) + # * +:default_value+ - (String) + # * +:description+ - (String) + # * +:apply_type+ - (String) + # * +:data_type+ - (String) + # * +:allowed_values+ - (String) + # * +:is_modifiable+ - (Boolean) + # * +:is_collection+ - (Boolean) # * +:db_security_group_memberships+ - (Array) # * +:db_security_group_name+ - (String) # * +:status+ - (String) + # * +:vpc_security_group_memberships+ - (Array) + # * +:vpc_security_group_id+ - (String) + # * +:status+ - (String) # * +:allows_vpc_and_non_vpc_instance_memberships+ - (Boolean) # * +:vpc_id+ - (String) @@ -466,6 +599,9 @@ class Client < Core::QueryClient # * +:db_security_groups+ - (Array) # * +:db_security_group_name+ - (String) # * +:status+ - (String) + # * +:vpc_security_groups+ - (Array) + # * +:vpc_security_group_id+ - (String) + # * +:status+ - (String) # * +:db_parameter_groups+ - (Array) # * +:db_parameter_group_name+ - (String) # * +:parameter_apply_status+ - (String) @@ -491,6 +627,7 @@ class Client < Core::QueryClient # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) # * +:iops+ - (Integer) + # * +:db_instance_identifier+ - (String) # * +:latest_restorable_time+ - (Time) # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) @@ -499,10 +636,12 @@ class Client < Core::QueryClient # * +:read_replica_db_instance_identifiers+ - (Array) # * +:license_model+ - (String) # * +:iops+ - (Integer) - # * +:option_group_membership+ - (Hash) + # * +:option_group_memberships+ - (Array) # * +:option_group_name+ - (String) # * +:status+ - (String) # * +:character_set_name+ - (String) + # * +:secondary_availability_zone+ - (String) + # * +:publicly_accessible+ - (Boolean) # @!method delete_db_parameter_group(options = {}) # Calls the DeleteDBParameterGroup API operation. @@ -542,6 +681,7 @@ class Client < Core::QueryClient # * +:license_model+ - (String) # * +:snapshot_type+ - (String) # * +:iops+ - (Integer) + # * +:option_group_name+ - (String) # @!method delete_db_subnet_group(options = {}) # Calls the DeleteDBSubnetGroup API operation. @@ -553,6 +693,24 @@ class Client < Core::QueryClient # contain two consecutive hyphens # @return [Core::Response] + # @!method delete_event_subscription(options = {}) + # Calls the DeleteEventSubscription API operation. + # @param [Hash] options + # * +:subscription_name+ - *required* - (String) The name of the RDS + # event notification subscription you want to delete. + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:customer_aws_id+ - (String) + # * +:cust_subscription_id+ - (String) + # * +:sns_topic_arn+ - (String) + # * +:status+ - (String) + # * +:subscription_creation_time+ - (String) + # * +:source_type+ - (String) + # * +:source_ids_list+ - (Array) + # * +:event_categories_list+ - (Array) + # * +:enabled+ - (Boolean) + # @!method delete_option_group(options = {}) # Calls the DeleteOptionGroup API operation. # @param [Hash] options @@ -637,6 +795,9 @@ class Client < Core::QueryClient # * +:db_security_groups+ - (Array) # * +:db_security_group_name+ - (String) # * +:status+ - (String) + # * +:vpc_security_groups+ - (Array) + # * +:vpc_security_group_id+ - (String) + # * +:status+ - (String) # * +:db_parameter_groups+ - (Array) # * +:db_parameter_group_name+ - (String) # * +:parameter_apply_status+ - (String) @@ -662,6 +823,7 @@ class Client < Core::QueryClient # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) # * +:iops+ - (Integer) + # * +:db_instance_identifier+ - (String) # * +:latest_restorable_time+ - (Time) # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) @@ -670,10 +832,42 @@ class Client < Core::QueryClient # * +:read_replica_db_instance_identifiers+ - (Array) # * +:license_model+ - (String) # * +:iops+ - (Integer) - # * +:option_group_membership+ - (Hash) + # * +:option_group_memberships+ - (Array) # * +:option_group_name+ - (String) # * +:status+ - (String) # * +:character_set_name+ - (String) + # * +:secondary_availability_zone+ - (String) + # * +:publicly_accessible+ - (Boolean) + + # @!method describe_db_log_files(options = {}) + # Calls the DescribeDBLogFiles API operation. + # @param [Hash] options + # * +:db_instance_identifier+ - (String) The customer-assigned name of + # the DB Instance that contains the log files you want to list. + # Constraints: Must contain from 1 to 63 alphanumeric characters or + # hyphens First character must be a letter Cannot end with a hyphen + # or contain two consecutive hyphens + # * +:filename_contains+ - (String) Filters the available log files for + # log file names that contain the specified string. + # * +:file_last_written+ - (Integer) Filters the available log files + # for files written since the specified date. + # * +:file_size+ - (Integer) Filters the available log files for files + # larger than the specified size. + # * +:max_records+ - (Integer) The maximum number of records to include + # in the response. If more records exist than the specified + # MaxRecords value, a pagination token called a marker is included in + # the response so that the remaining results can be retrieved. + # * +:marker+ - (String) The pagination token provided in the previous + # request. If this parameter is specified the response includes only + # records beyond the marker, up to MaxRecords. + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:describe_db_log_files+ - (Array) + # * +:log_file_name+ - (String) + # * +:last_written+ - (Integer) + # * +:size+ - (Integer) + # * +:marker+ - (String) # @!method describe_db_parameter_groups(options = {}) # Calls the DescribeDBParameterGroups API operation. @@ -796,6 +990,7 @@ class Client < Core::QueryClient # * +:license_model+ - (String) # * +:snapshot_type+ - (String) # * +:iops+ - (Integer) + # * +:option_group_name+ - (String) # @!method describe_db_subnet_groups(options = {}) # Calls the DescribeDBSubnetGroups API operation. @@ -857,6 +1052,48 @@ class Client < Core::QueryClient # * +:minimum_engine_version+ - (String) # * +:apply_method+ - (String) + # @!method describe_event_categories(options = {}) + # Calls the DescribeEventCategories API operation. + # @param [Hash] options + # * +:source_type+ - (String) The type of source that will be + # generating the events. Valid values: db-instance | + # db-parameter-group | db-security-group | db-snapshot + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:event_categories_map_list+ - (Array) + # * +:source_type+ - (String) + # * +:event_categories+ - (Array) + + # @!method describe_event_subscriptions(options = {}) + # Calls the DescribeEventSubscriptions API operation. + # @param [Hash] options + # * +:subscription_name+ - (String) The name of the RDS event + # notification subscription you want to describe. + # * +:max_records+ - (Integer) The maximum number of records to include + # in the response. If more records exist than the specified + # MaxRecords value, a pagination token called a marker is included in + # the response so that the remaining results can be retrieved. + # Default: 100 Constraints: minimum 20, maximum 100 + # * +:marker+ - (String) An optional pagination token provided by a + # previous DescribeOrderableDBInstanceOptions request. If this + # parameter is specified, the response includes only records beyond + # the marker, up to the value specified by MaxRecords . + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:marker+ - (String) + # * +:event_subscriptions_list+ - (Array) + # * +:customer_aws_id+ - (String) + # * +:cust_subscription_id+ - (String) + # * +:sns_topic_arn+ - (String) + # * +:status+ - (String) + # * +:subscription_creation_time+ - (String) + # * +:source_type+ - (String) + # * +:source_ids_list+ - (Array) + # * +:event_categories_list+ - (Array) + # * +:enabled+ - (Boolean) + # @!method describe_events(options = {}) # Calls the DescribeEvents API operation. # @param [Hash] options @@ -872,6 +1109,8 @@ class Client < Core::QueryClient # format. # * +:duration+ - (Integer) The number of minutes to retrieve events # for. + # * +:event_categories+ - (Array) A list of event categories + # that trigger notifications for a event notification subscription. # * +:max_records+ - (Integer) The maximum number of records to include # in the response. If more records exist than the specified # MaxRecords value, a marker is included in the response so that the @@ -888,6 +1127,7 @@ class Client < Core::QueryClient # * +:source_identifier+ - (String) # * +:source_type+ - (String) # * +:message+ - (String) + # * +:event_categories+ - (Array) # * +:date+ - (Time) # @!method describe_option_group_options(options = {}) @@ -898,8 +1138,15 @@ class Client < Core::QueryClient # * +:major_engine_version+ - (String) If specified, filters the # results to include only options for the specified major engine # version. - # * +:max_records+ - (Integer) - # * +:marker+ - (String) + # * +:max_records+ - (Integer) The maximum number of records to include + # in the response. If more records exist than the specified + # MaxRecords value, a pagination token called a marker is included in + # the response so that the remaining results can be retrieved. + # Default: 100 Constraints: minimum 20, maximum 100 + # * +:marker+ - (String) An optional pagination token provided by a + # previous request. If this parameter is specified, the response + # includes only records beyond the marker, up to the value specified + # by MaxRecords. # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -912,6 +1159,14 @@ class Client < Core::QueryClient # * +:port_required+ - (Boolean) # * +:default_port+ - (Integer) # * +:options_depended_on+ - (Array) + # * +:persistent+ - (Boolean) + # * +:option_group_option_settings+ - (Array) + # * +:setting_name+ - (String) + # * +:setting_description+ - (String) + # * +:default_value+ - (String) + # * +:apply_type+ - (String) + # * +:allowed_values+ - (String) + # * +:is_modifiable+ - (Boolean) # * +:marker+ - (String) # @!method describe_option_groups(options = {}) @@ -920,8 +1175,15 @@ class Client < Core::QueryClient # * +:option_group_name+ - (String) The name of the option group to # describe. Cannot be supplied together with EngineName or # MajorEngineVersion. - # * +:marker+ - (String) - # * +:max_records+ - (Integer) + # * +:marker+ - (String) An optional pagination token provided by a + # previous DescribeOptionGroups request. If this parameter is + # specified, the response includes only records beyond the marker, up + # to the value specified by MaxRecords. + # * +:max_records+ - (Integer) The maximum number of records to include + # in the response. If more records exist than the specified + # MaxRecords value, a pagination token called a marker is included in + # the response so that the remaining results can be retrieved. + # Default: 100 Constraints: minimum 20, maximum 100 # * +:engine_name+ - (String) Filters the list of option groups to only # include groups associated with a specific database engine. # * +:major_engine_version+ - (String) Filters the list of option @@ -939,10 +1201,24 @@ class Client < Core::QueryClient # * +:options+ - (Array) # * +:option_name+ - (String) # * +:option_description+ - (String) + # * +:persistent+ - (Boolean) # * +:port+ - (Integer) + # * +:option_settings+ - (Array) + # * +:name+ - (String) + # * +:value+ - (String) + # * +:default_value+ - (String) + # * +:description+ - (String) + # * +:apply_type+ - (String) + # * +:data_type+ - (String) + # * +:allowed_values+ - (String) + # * +:is_modifiable+ - (Boolean) + # * +:is_collection+ - (Boolean) # * +:db_security_group_memberships+ - (Array) # * +:db_security_group_name+ - (String) # * +:status+ - (String) + # * +:vpc_security_group_memberships+ - (Array) + # * +:vpc_security_group_id+ - (String) + # * +:status+ - (String) # * +:allows_vpc_and_non_vpc_instance_memberships+ - (Boolean) # * +:vpc_id+ - (String) # * +:marker+ - (String) @@ -1090,6 +1366,23 @@ class Client < Core::QueryClient # * +:recurring_charge_amount+ - (Numeric) # * +:recurring_charge_frequency+ - (String) + # @!method download_db_log_file_portion(options = {}) + # Calls the DownloadDBLogFilePortion API operation. + # @param [Hash] options + # * +:db_instance_identifier+ - (String) + # * +:log_file_name+ - (String) + # * +:marker+ - (String) The pagination token provided in the previous + # request. If this parameter is specified the response includes only + # records beyond the marker, up to MaxRecords. + # * +:number_of_lines+ - (Integer) The number of lines remaining to be + # downloaded. + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:log_file_data+ - (String) + # * +:marker+ - (String) + # * +:additional_data_pending+ - (Boolean) + # @!method list_tags_for_resource(options = {}) # Calls the ListTagsForResource API operation. # @param [Hash] options @@ -1119,6 +1412,11 @@ class Client < Core::QueryClient # * +:db_security_groups+ - (Array) A list of DB Security # Groups to authorize on this DB Instance. This change is # asynchronously applied as soon as possible. + # * +:vpc_security_group_ids+ - (Array) A list of Ec2 Vpc + # Security Groups to authorize on this DB Instance. This change is + # asynchronously applied as soon as possible. Constraints: Must be 1 + # to 255 alphanumeric characters First character must be a letter + # Cannot end with a hyphen or contain two consecutive hyphens # * +:apply_immediately+ - (Boolean) Specifies whether or not the # modifications in this request and any pending modifications are # asynchronously applied as soon as possible, regardless of the @@ -1175,6 +1473,12 @@ class Client < Core::QueryClient # they are 10% greater than the current value. Type: Integer # * +:option_group_name+ - (String) Indicates that the DB Instance # should be associated with the specified option group. + # * +:new_db_instance_identifier+ - (String) The new DB Instance + # identifier for the DB Instance when renaming a DB Instance. This + # value is stored as a lowercase string. Constraints: Must contain + # from 1 to 63 alphanumeric characters or hyphens First character + # must be a letter Cannot end with a hyphen or contain two + # consecutive hyphens # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -1194,6 +1498,9 @@ class Client < Core::QueryClient # * +:db_security_groups+ - (Array) # * +:db_security_group_name+ - (String) # * +:status+ - (String) + # * +:vpc_security_groups+ - (Array) + # * +:vpc_security_group_id+ - (String) + # * +:status+ - (String) # * +:db_parameter_groups+ - (Array) # * +:db_parameter_group_name+ - (String) # * +:parameter_apply_status+ - (String) @@ -1219,6 +1526,7 @@ class Client < Core::QueryClient # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) # * +:iops+ - (Integer) + # * +:db_instance_identifier+ - (String) # * +:latest_restorable_time+ - (Time) # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) @@ -1227,10 +1535,12 @@ class Client < Core::QueryClient # * +:read_replica_db_instance_identifiers+ - (Array) # * +:license_model+ - (String) # * +:iops+ - (Integer) - # * +:option_group_membership+ - (Hash) + # * +:option_group_memberships+ - (Array) # * +:option_group_name+ - (String) # * +:status+ - (String) # * +:character_set_name+ - (String) + # * +:secondary_availability_zone+ - (String) + # * +:publicly_accessible+ - (Boolean) # @!method modify_db_parameter_group(options = {}) # Calls the ModifyDBParameterGroup API operation. @@ -1292,6 +1602,40 @@ class Client < Core::QueryClient # * +:provisioned_iops_capable+ - (Boolean) # * +:subnet_status+ - (String) + # @!method modify_event_subscription(options = {}) + # Calls the ModifyEventSubscription API operation. + # @param [Hash] options + # * +:subscription_name+ - *required* - (String) The name of the RDS + # event notification subscription. + # * +:sns_topic_arn+ - (String) The Amazon Resource Name (ARN) of the + # SNS topic created for event notification. The ARN is created by + # Amazon SNS when you create a topic and subscribe to it. + # * +:source_type+ - (String) The type of source that will be + # generating the events. For example, if you want to be notified of + # events generated by a DB instance, you would set this parameter to + # db-instance. if this value is not specified, all events are + # returned. Valid values: db-instance | db-parameter-group | + # db-security-group | db-snapshot + # * +:event_categories+ - (Array) A list of event categories + # for a SourceType that you want to subscribe to. You can see a list + # of the categories for a given SourceType in the Events topic in the + # Amazon RDS User Guide or by using the DescribeEventCategories + # action. + # * +:enabled+ - (Boolean) A Boolean value; set to +true+ to activate + # the subscription. + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:customer_aws_id+ - (String) + # * +:cust_subscription_id+ - (String) + # * +:sns_topic_arn+ - (String) + # * +:status+ - (String) + # * +:subscription_creation_time+ - (String) + # * +:source_type+ - (String) + # * +:source_ids_list+ - (Array) + # * +:event_categories_list+ - (Array) + # * +:enabled+ - (Boolean) + # @!method modify_option_group(options = {}) # Calls the ModifyOptionGroup API operation. # @param [Hash] options @@ -1300,9 +1644,29 @@ class Client < Core::QueryClient # * +:options_to_include+ - (Array) Options in this list are # added to the Option Group or, if already present, the specified # configuration is used to update the existing configuration. - # * +:option_name+ - *required* - (String) - # * +:port+ - (Integer) - # * +:db_security_group_memberships+ - (Array) + # * +:option_name+ - *required* - (String) The configuration of + # options to include in a group. + # * +:port+ - (Integer) The optional port for the option. + # * +:db_security_group_memberships+ - (Array) A list of + # DBSecurityGroupMemebrship name strings used for this option. + # * +:vpc_security_group_memberships+ - (Array) A list of + # VpcSecurityGroupMemebrship name strings used for this option. + # * +:option_settings+ - (Array) A list of option settings + # applied for this option. + # * +:name+ - (String) The name of the setting. + # * +:value+ - (String) The value of this setting. + # * +:default_value+ - (String) Default value for this setting. + # * +:description+ - (String) The description of the setting. + # * +:apply_type+ - (String) Specifies the apply type for this + # setting. + # * +:data_type+ - (String) Specifies the valid data type of this + # setting + # * +:allowed_values+ - (String) Specifies a valid list/range of + # values allowed for this setting. + # * +:is_modifiable+ - (Boolean) Indicates if the setting is + # modifiable or not. + # * +:is_collection+ - (Boolean) Indicates if the value for the + # setting can be a list of values or a single value. # * +:options_to_remove+ - (Array) Options in this list are # removed from the Option Group. # * +:apply_immediately+ - (Boolean) Indicates whether the changes @@ -1318,10 +1682,24 @@ class Client < Core::QueryClient # * +:options+ - (Array) # * +:option_name+ - (String) # * +:option_description+ - (String) + # * +:persistent+ - (Boolean) # * +:port+ - (Integer) + # * +:option_settings+ - (Array) + # * +:name+ - (String) + # * +:value+ - (String) + # * +:default_value+ - (String) + # * +:description+ - (String) + # * +:apply_type+ - (String) + # * +:data_type+ - (String) + # * +:allowed_values+ - (String) + # * +:is_modifiable+ - (Boolean) + # * +:is_collection+ - (Boolean) # * +:db_security_group_memberships+ - (Array) # * +:db_security_group_name+ - (String) # * +:status+ - (String) + # * +:vpc_security_group_memberships+ - (Array) + # * +:vpc_security_group_id+ - (String) + # * +:status+ - (String) # * +:allows_vpc_and_non_vpc_instance_memberships+ - (Boolean) # * +:vpc_id+ - (String) @@ -1370,6 +1748,9 @@ class Client < Core::QueryClient # * +:db_security_groups+ - (Array) # * +:db_security_group_name+ - (String) # * +:status+ - (String) + # * +:vpc_security_groups+ - (Array) + # * +:vpc_security_group_id+ - (String) + # * +:status+ - (String) # * +:db_parameter_groups+ - (Array) # * +:db_parameter_group_name+ - (String) # * +:parameter_apply_status+ - (String) @@ -1395,6 +1776,7 @@ class Client < Core::QueryClient # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) # * +:iops+ - (Integer) + # * +:db_instance_identifier+ - (String) # * +:latest_restorable_time+ - (Time) # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) @@ -1403,10 +1785,12 @@ class Client < Core::QueryClient # * +:read_replica_db_instance_identifiers+ - (Array) # * +:license_model+ - (String) # * +:iops+ - (Integer) - # * +:option_group_membership+ - (Hash) + # * +:option_group_memberships+ - (Array) # * +:option_group_name+ - (String) # * +:status+ - (String) # * +:character_set_name+ - (String) + # * +:secondary_availability_zone+ - (String) + # * +:publicly_accessible+ - (Boolean) # @!method purchase_reserved_db_instances_offering(options = {}) # Calls the PurchaseReservedDBInstancesOffering API operation. @@ -1465,6 +1849,9 @@ class Client < Core::QueryClient # * +:db_security_groups+ - (Array) # * +:db_security_group_name+ - (String) # * +:status+ - (String) + # * +:vpc_security_groups+ - (Array) + # * +:vpc_security_group_id+ - (String) + # * +:status+ - (String) # * +:db_parameter_groups+ - (Array) # * +:db_parameter_group_name+ - (String) # * +:parameter_apply_status+ - (String) @@ -1490,6 +1877,7 @@ class Client < Core::QueryClient # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) # * +:iops+ - (Integer) + # * +:db_instance_identifier+ - (String) # * +:latest_restorable_time+ - (Time) # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) @@ -1498,10 +1886,34 @@ class Client < Core::QueryClient # * +:read_replica_db_instance_identifiers+ - (Array) # * +:license_model+ - (String) # * +:iops+ - (Integer) - # * +:option_group_membership+ - (Hash) + # * +:option_group_memberships+ - (Array) # * +:option_group_name+ - (String) # * +:status+ - (String) # * +:character_set_name+ - (String) + # * +:secondary_availability_zone+ - (String) + # * +:publicly_accessible+ - (Boolean) + + # @!method remove_source_identifier_from_subscription(options = {}) + # Calls the RemoveSourceIdentifierFromSubscription API operation. + # @param [Hash] options + # * +:subscription_name+ - *required* - (String) The name of the RDS + # event notification subscription you want to remove a source + # identifier from. + # * +:source_identifier+ - *required* - (String) The source identifier + # to be removed from the subscription, such as the DB instance + # identifier for a DB instance or the name of a security group. + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:customer_aws_id+ - (String) + # * +:cust_subscription_id+ - (String) + # * +:sns_topic_arn+ - (String) + # * +:status+ - (String) + # * +:subscription_creation_time+ - (String) + # * +:source_type+ - (String) + # * +:source_ids_list+ - (Array) + # * +:event_categories_list+ - (Array) + # * +:enabled+ - (Boolean) # @!method remove_tags_from_resource(options = {}) # Calls the RemoveTagsFromResource API operation. @@ -1569,6 +1981,19 @@ class Client < Core::QueryClient # * +:multi_az+ - (Boolean) Specifies if the DB Instance is a Multi-AZ # deployment. Constraint: You cannot specify the AvailabilityZone # parameter if the MultiAZ parameter is set to +true+ . + # * +:publicly_accessible+ - (Boolean) Specifies the accessibility + # options for the DB Instance. A value of +true+ specifies an + # Internet-facing instance with a publicly resolvable DNS name, which + # resolves to a public IP address. A value of +false+ specifies an + # internal instance with a DNS name that resolves to a private IP + # address. Default: The default behavior varies depending on whether + # a VPC has been requested or not. The following list shows the + # default behavior in each case. Default VPC: +true+ VPC: +false+ If + # no DB subnet group has been specified as part of the request and + # the PubliclyAccessible value has not been set, the DB instance will + # be publicly accessible. If a specific DB subnet group has been + # specified as part of the request and the PubliclyAccessible value + # has not been set, the DB instance will be private. # * +:auto_minor_version_upgrade+ - (Boolean) Indicates that minor # version upgrades will be applied automatically to the DB Instance # during the maintenance window. @@ -1603,6 +2028,9 @@ class Client < Core::QueryClient # * +:db_security_groups+ - (Array) # * +:db_security_group_name+ - (String) # * +:status+ - (String) + # * +:vpc_security_groups+ - (Array) + # * +:vpc_security_group_id+ - (String) + # * +:status+ - (String) # * +:db_parameter_groups+ - (Array) # * +:db_parameter_group_name+ - (String) # * +:parameter_apply_status+ - (String) @@ -1628,6 +2056,7 @@ class Client < Core::QueryClient # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) # * +:iops+ - (Integer) + # * +:db_instance_identifier+ - (String) # * +:latest_restorable_time+ - (Time) # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) @@ -1636,10 +2065,12 @@ class Client < Core::QueryClient # * +:read_replica_db_instance_identifiers+ - (Array) # * +:license_model+ - (String) # * +:iops+ - (Integer) - # * +:option_group_membership+ - (Hash) + # * +:option_group_memberships+ - (Array) # * +:option_group_name+ - (String) # * +:status+ - (String) # * +:character_set_name+ - (String) + # * +:secondary_availability_zone+ - (String) + # * +:publicly_accessible+ - (Boolean) # @!method restore_db_instance_to_point_in_time(options = {}) # Calls the RestoreDBInstanceToPointInTime API operation. @@ -1664,6 +2095,19 @@ class Client < Core::QueryClient # * +:multi_az+ - (Boolean) Specifies if the DB Instance is a Multi-AZ # deployment. Constraint: You cannot specify the AvailabilityZone # parameter if the MultiAZ parameter is set to +true+ . + # * +:publicly_accessible+ - (Boolean) Specifies the accessibility + # options for the DB Instance. A value of +true+ specifies an + # Internet-facing instance with a publicly resolvable DNS name, which + # resolves to a public IP address. A value of +false+ specifies an + # internal instance with a DNS name that resolves to a private IP + # address. Default: The default behavior varies depending on whether + # a VPC has been requested or not. The following list shows the + # default behavior in each case. Default VPC: +true+ VPC: +false+ If + # no DB subnet group has been specified as part of the request and + # the PubliclyAccessible value has not been set, the DB instance will + # be publicly accessible. If a specific DB subnet group has been + # specified as part of the request and the PubliclyAccessible value + # has not been set, the DB instance will be private. # * +:auto_minor_version_upgrade+ - (Boolean) Indicates that minor # version upgrades will be applied automatically to the DB Instance # during the maintenance window. @@ -1698,6 +2142,9 @@ class Client < Core::QueryClient # * +:db_security_groups+ - (Array) # * +:db_security_group_name+ - (String) # * +:status+ - (String) + # * +:vpc_security_groups+ - (Array) + # * +:vpc_security_group_id+ - (String) + # * +:status+ - (String) # * +:db_parameter_groups+ - (Array) # * +:db_parameter_group_name+ - (String) # * +:parameter_apply_status+ - (String) @@ -1723,6 +2170,7 @@ class Client < Core::QueryClient # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) # * +:iops+ - (Integer) + # * +:db_instance_identifier+ - (String) # * +:latest_restorable_time+ - (Time) # * +:multi_az+ - (Boolean) # * +:engine_version+ - (String) @@ -1731,10 +2179,12 @@ class Client < Core::QueryClient # * +:read_replica_db_instance_identifiers+ - (Array) # * +:license_model+ - (String) # * +:iops+ - (Integer) - # * +:option_group_membership+ - (Hash) + # * +:option_group_memberships+ - (Array) # * +:option_group_name+ - (String) # * +:status+ - (String) # * +:character_set_name+ - (String) + # * +:secondary_availability_zone+ - (String) + # * +:publicly_accessible+ - (Boolean) # @!method revoke_db_security_group_ingress(options = {}) # Calls the RevokeDBSecurityGroupIngress API operation. @@ -1771,7 +2221,7 @@ class Client < Core::QueryClient # end client methods # - define_client_methods('2012-09-17') + define_client_methods('2013-02-12') end end From ad5f574ac5ad17e70df5412cce427ff3313235be Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Tue, 12 Mar 2013 14:51:49 -0700 Subject: [PATCH 0463/1398] Marked a fragile test as pending until it can be refactored. --- spec/net/http/connection_pool/live_tests_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/net/http/connection_pool/live_tests_spec.rb b/spec/net/http/connection_pool/live_tests_spec.rb index b2abcb88ff3..63a373ea4f4 100644 --- a/spec/net/http/connection_pool/live_tests_spec.rb +++ b/spec/net/http/connection_pool/live_tests_spec.rb @@ -43,6 +43,7 @@ end it 'creates connections as needed' do + pending threads = [] 3.downto(1) do |n| From 0b81f14c53ae5110cdc85634caa768ef15e8fcca Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Tue, 12 Mar 2013 17:05:23 -0700 Subject: [PATCH 0464/1398] Fixed an issue with DescribeVpcAttribute. --- lib/aws/api_config/EC2-2013-02-01.yml | 5 ++--- lib/aws/ec2/client.rb | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/aws/api_config/EC2-2013-02-01.yml b/lib/aws/api_config/EC2-2013-02-01.yml index 44db104e760..af7083c9537 100644 --- a/lib/aws/api_config/EC2-2013-02-01.yml +++ b/lib/aws/api_config/EC2-2013-02-01.yml @@ -2874,9 +2874,7 @@ VpcId: - :string - :required - EnableDnsSupport: - - :string - EnableDnsHostnames: + Attribute: - :string :outputs: :children: @@ -3518,6 +3516,7 @@ :inputs: VpcId: - :string + - :required - :rename: VpcId EnableDnsSupport: - :structure: diff --git a/lib/aws/ec2/client.rb b/lib/aws/ec2/client.rb index 19349ba557c..b4bce455716 100644 --- a/lib/aws/ec2/client.rb +++ b/lib/aws/ec2/client.rb @@ -2372,8 +2372,7 @@ class Client < Core::QueryClient # Calls the DescribeVpcAttribute API operation. # @param [Hash] options # * +:vpc_id+ - *required* - (String) - # * +:enable_dns_support+ - (String) - # * +:enable_dns_hostnames+ - (String) + # * +:attribute+ - (String) # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -2888,7 +2887,7 @@ class Client < Core::QueryClient # @!method modify_vpc_attribute(options = {}) # Calls the ModifyVpcAttribute API operation. # @param [Hash] options - # * +:vpc_id+ - (String) + # * +:vpc_id+ - *required* - (String) # * +:enable_dns_support+ - (Hash) # * +:value+ - (Boolean) Boolean value # * +:enable_dns_hostnames+ - (Hash) From 15c612a43c96bcbc927740a05415eac8f6455477 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Wed, 13 Mar 2013 14:33:34 -0700 Subject: [PATCH 0465/1398] Bump version to 1.8.5 --- ChangeLog | 36 ++++++++++++++++++++++++++++++++++++ lib/aws/version.rb | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 026f816e104..048cfc9aac9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,39 @@ +2013-03-12 Trevor Rowe + + * lib/aws/api_config/EC2-2013-02-01.yml, lib/aws/ec2/client.rb: Fixed an + issue with DescribeVpcAttribute. + + * spec/net/http/connection_pool/live_tests_spec.rb: Marked a fragile test as + pending until it can be refactored. + + * lib/aws/api_config/RDS-2013-02-12.yml, lib/aws/rds/client.rb: Updated RDS + client to the latest API version (2013-02-12). + + * lib/aws/ec2/snapshot_collection.rb, + spec/aws/ec2/snapshot_collection_spec.rb: EC2::SnapshotCollection now + populates snapshots when enumerating. + + * lib/aws/s3/s3_object.rb: Added missing documentation for the :expires + option to S3Object#write. + +2013-03-13 Mahito + + * lib/aws/core.rb, lib/aws/core/configuration.rb: Changed String to Boolean + with type of:dynamo_db_retry_throughput_errors + +2013-03-11 Trevor Rowe + + * features/dynamo_db/step_definitions/dynamo_db.rb: Removed and unecessary + service call from DynamoDB features. + + * ChangeLog, lib/aws/version.rb: Bump version to 1.8.4 + + * lib/aws/ec2/client.rb: Bumped AWS::EC2::Client api version. + + * lib/aws/api_config/EC2-2012-12-01.yml, + lib/aws/api_config/EC2-2013-02-01.yml, lib/aws/ec2/client.rb: Updated the + EC2 API configuration to the 2013-02-01 API version. + 2013-03-10 Mike Ferrier * lib/aws/s3/cors_rule_collection.rb: Better fix for the delete_if bug diff --git a/lib/aws/version.rb b/lib/aws/version.rb index eb8d4ddff8d..2cf27bdab1d 100644 --- a/lib/aws/version.rb +++ b/lib/aws/version.rb @@ -14,5 +14,5 @@ module AWS # Current version of the AWS SDK for Ruby - VERSION = '1.8.4' + VERSION = '1.8.5' end From 8b9eb7445a7c3dba85fd65d51e0af05bf5766457 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Thu, 14 Mar 2013 14:13:57 -0700 Subject: [PATCH 0466/1398] Added documentation for for AWS::CloudWatch::Client operations. --- lib/aws/cloud_watch/client.rb | 362 +++++++++++++++++++++++++++------- 1 file changed, 288 insertions(+), 74 deletions(-) diff --git a/lib/aws/cloud_watch/client.rb b/lib/aws/cloud_watch/client.rb index 5af7ebc29f1..cec46bb0662 100644 --- a/lib/aws/cloud_watch/client.rb +++ b/lib/aws/cloud_watch/client.rb @@ -30,18 +30,27 @@ class Client < Core::QueryClient # @!method delete_alarms(options = {}) # Calls the DeleteAlarms API operation. # @param [Hash] options - # * +:alarm_names+ - *required* - (Array) + # * +:alarm_names+ - *required* - (Array) A list of alarms to + # be deleted. # @return [Core::Response] # @!method describe_alarm_history(options = {}) # Calls the DescribeAlarmHistory API operation. # @param [Hash] options - # * +:alarm_name+ - (String) - # * +:history_item_type+ - (String) - # * +:start_date+ - (String) - # * +:end_date+ - (String) - # * +:max_records+ - (Integer) - # * +:next_token+ - (String) + # * +:alarm_name+ - (String) The name of the alarm. + # * +:history_item_type+ - (String) The type of alarm histories to + # retrieve. Valid values include: + # * +ConfigurationUpdate+ + # * +StateUpdate+ + # * +Action+ + # * +:start_date+ - (String) The starting date to + # retrieve alarm history. + # * +:end_date+ - (String) The ending date to + # retrieve alarm history. + # * +:max_records+ - (Integer) The maximum number of alarm history + # records to retrieve. + # * +:next_token+ - (String) The token returned by a previous call to + # indicate that there is more data available. # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -56,12 +65,20 @@ class Client < Core::QueryClient # @!method describe_alarms(options = {}) # Calls the DescribeAlarms API operation. # @param [Hash] options - # * +:alarm_names+ - (Array) - # * +:alarm_name_prefix+ - (String) - # * +:state_value+ - (String) - # * +:action_prefix+ - (String) - # * +:max_records+ - (Integer) - # * +:next_token+ - (String) + # * +:alarm_names+ - (Array) A list of alarm names to retrieve + # information for. + # * +:alarm_name_prefix+ - (String) The alarm name prefix. AlarmNames + # cannot be specified if this parameter is specified. + # * +:state_value+ - (String) The state value to be used in matching + # alarms. Valid values include: + # * +OK+ + # * +ALARM+ + # * +INSUFFICIENT_DATA+ + # * +:action_prefix+ - (String) The action name prefix. + # * +:max_records+ - (Integer) The maximum number of alarm descriptions + # to retrieve. + # * +:next_token+ - (String) The token returned by a previous call to + # indicate that there is more data available. # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -94,14 +111,50 @@ class Client < Core::QueryClient # @!method describe_alarms_for_metric(options = {}) # Calls the DescribeAlarmsForMetric API operation. # @param [Hash] options - # * +:metric_name+ - *required* - (String) - # * +:namespace+ - *required* - (String) - # * +:statistic+ - (String) - # * +:dimensions+ - (Array) - # * +:name+ - *required* - (String) - # * +:value+ - *required* - (String) - # * +:period+ - (Integer) - # * +:unit+ - (String) + # * +:metric_name+ - *required* - (String) The name of the metric. + # * +:namespace+ - *required* - (String) The namespace of the metric. + # * +:statistic+ - (String) The statistic for the metric. Valid values + # include: + # * +SampleCount+ + # * +Average+ + # * +Sum+ + # * +Minimum+ + # * +Maximum+ + # * +:dimensions+ - (Array) The list of dimensions associated + # with the metric. + # * +:name+ - *required* - (String) The name of the dimension. + # * +:value+ - *required* - (String) The value representing the + # dimension measurement + # * +:period+ - (Integer) The period in seconds over which the + # statistic is applied. + # * +:unit+ - (String) The unit for the metric. Valid values include: + # * +Seconds+ + # * +Microseconds+ + # * +Milliseconds+ + # * +Bytes+ + # * +Kilobytes+ + # * +Megabytes+ + # * +Gigabytes+ + # * +Terabytes+ + # * +Bits+ + # * +Kilobits+ + # * +Megabits+ + # * +Gigabits+ + # * +Terabits+ + # * +Percent+ + # * +Count+ + # * +Bytes/Second+ + # * +Kilobytes/Second+ + # * +Megabytes/Second+ + # * +Gigabytes/Second+ + # * +Terabytes/Second+ + # * +Bits/Second+ + # * +Kilobits/Second+ + # * +Megabits/Second+ + # * +Gigabits/Second+ + # * +Terabits/Second+ + # * +Count/Second+ + # * +None+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -133,28 +186,72 @@ class Client < Core::QueryClient # @!method disable_alarm_actions(options = {}) # Calls the DisableAlarmActions API operation. # @param [Hash] options - # * +:alarm_names+ - *required* - (Array) + # * +:alarm_names+ - *required* - (Array) The names of the + # alarms to disable actions for. # @return [Core::Response] # @!method enable_alarm_actions(options = {}) # Calls the EnableAlarmActions API operation. # @param [Hash] options - # * +:alarm_names+ - *required* - (Array) + # * +:alarm_names+ - *required* - (Array) The names of the + # alarms to enable actions for. # @return [Core::Response] # @!method get_metric_statistics(options = {}) # Calls the GetMetricStatistics API operation. # @param [Hash] options - # * +:namespace+ - *required* - (String) - # * +:metric_name+ - *required* - (String) - # * +:dimensions+ - (Array) - # * +:name+ - *required* - (String) - # * +:value+ - *required* - (String) - # * +:start_time+ - *required* - (String) - # * +:end_time+ - *required* - (String) - # * +:period+ - *required* - (Integer) - # * +:statistics+ - *required* - (Array) - # * +:unit+ - (String) + # * +:namespace+ - *required* - (String) The namespace of the metric. + # * +:metric_name+ - *required* - (String) The name of the metric. + # * +:dimensions+ - (Array) A list of dimensions describing + # qualities of the metric. + # * +:name+ - *required* - (String) The name of the dimension. + # * +:value+ - *required* - (String) The value representing the + # dimension measurement + # * +:start_time+ - *required* - (String) The time + # stamp to use for determining the first datapoint to return. The + # value specified is inclusive; results include datapoints with the + # time stamp specified. The specified start time is rounded down to + # the nearest value. Datapoints are returned for start times up to + # two weeks in the past. Specified start times that are more than two + # weeks in the past will not return datapoints for metrics that are + # older than two weeks. + # * +:end_time+ - *required* - (String) The time + # stamp to use for determining the last datapoint to return. The + # value specified is exclusive; results will include datapoints up to + # the time stamp specified. + # * +:period+ - *required* - (Integer) The granularity, in seconds, of + # the returned datapoints. Period must be at least 60 seconds and + # must be a multiple of 60. The default value is 60. + # * +:statistics+ - *required* - (Array) The metric statistics + # to return. + # * +:unit+ - (String) The unit for the metric. Valid values include: + # * +Seconds+ + # * +Microseconds+ + # * +Milliseconds+ + # * +Bytes+ + # * +Kilobytes+ + # * +Megabytes+ + # * +Gigabytes+ + # * +Terabytes+ + # * +Bits+ + # * +Kilobits+ + # * +Megabits+ + # * +Gigabits+ + # * +Terabits+ + # * +Percent+ + # * +Count+ + # * +Bytes/Second+ + # * +Kilobytes/Second+ + # * +Megabytes/Second+ + # * +Gigabytes/Second+ + # * +Terabytes/Second+ + # * +Bits/Second+ + # * +Kilobits/Second+ + # * +Megabits/Second+ + # * +Gigabits/Second+ + # * +Terabits/Second+ + # * +Count/Second+ + # * +None+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -171,12 +268,16 @@ class Client < Core::QueryClient # @!method list_metrics(options = {}) # Calls the ListMetrics API operation. # @param [Hash] options - # * +:namespace+ - (String) - # * +:metric_name+ - (String) - # * +:dimensions+ - (Array) - # * +:name+ - *required* - (String) - # * +:value+ - (String) - # * +:next_token+ - (String) + # * +:namespace+ - (String) The namespace to filter against. + # * +:metric_name+ - (String) The name of the metric to filter against. + # * +:dimensions+ - (Array) A list of dimensions to filter + # against. + # * +:name+ - *required* - (String) The dimension name to be matched. + # * +:value+ - (String) The value of the dimension to be matched. + # Specifying a Name without specifying a Value returns all values + # associated with that Name. + # * +:next_token+ - (String) The token returned by a previous call to + # indicate that there is more data available. # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -191,51 +292,164 @@ class Client < Core::QueryClient # @!method put_metric_alarm(options = {}) # Calls the PutMetricAlarm API operation. # @param [Hash] options - # * +:alarm_name+ - *required* - (String) - # * +:alarm_description+ - (String) - # * +:actions_enabled+ - (Boolean) - # * +:ok_actions+ - (Array) - # * +:alarm_actions+ - (Array) - # * +:insufficient_data_actions+ - (Array) - # * +:metric_name+ - *required* - (String) - # * +:namespace+ - *required* - (String) - # * +:statistic+ - *required* - (String) - # * +:dimensions+ - (Array) - # * +:name+ - *required* - (String) - # * +:value+ - *required* - (String) - # * +:period+ - *required* - (Integer) - # * +:unit+ - (String) - # * +:evaluation_periods+ - *required* - (Integer) - # * +:threshold+ - *required* - (Float) - # * +:comparison_operator+ - *required* - (String) + # * +:alarm_name+ - *required* - (String) The descriptive name for the + # alarm. This name must be unique within the user's AWS account + # * +:alarm_description+ - (String) The description for the alarm. + # * +:actions_enabled+ - (Boolean) Indicates whether or not actions + # should be executed during any changes to the alarm's state. + # * +:ok_actions+ - (Array) The list of actions to execute when + # this alarm transitions into an OK state from any other state. Each + # action is specified as an Amazon Resource Number (ARN). Currently + # the only action supported is publishing to an Amazon SNS topic or + # an Amazon Auto Scaling policy. + # * +:alarm_actions+ - (Array) The list of actions to execute + # when this alarm transitions into an ALARM state from any other + # state. Each action is specified as an Amazon Resource Number (ARN). + # Currently the only action supported is publishing to an Amazon SNS + # topic or an Amazon Auto Scaling policy. + # * +:insufficient_data_actions+ - (Array) The list of actions + # to execute when this alarm transitions into an INSUFFICIENT_DATA + # state from any other state. Each action is specified as an Amazon + # Resource Number (ARN). Currently the only action supported is + # publishing to an Amazon SNS topic or an Amazon Auto Scaling policy. + # * +:metric_name+ - *required* - (String) The name for the alarm's + # associated metric. + # * +:namespace+ - *required* - (String) The namespace for the alarm's + # associated metric. + # * +:statistic+ - *required* - (String) The statistic to apply to the + # alarm's associated metric. Valid values include: + # * +SampleCount+ + # * +Average+ + # * +Sum+ + # * +Minimum+ + # * +Maximum+ + # * +:dimensions+ - (Array) The dimensions for the alarm's + # associated metric. + # * +:name+ - *required* - (String) The name of the dimension. + # * +:value+ - *required* - (String) The value representing the + # dimension measurement + # * +:period+ - *required* - (Integer) The period in seconds over which + # the specified statistic is applied. + # * +:unit+ - (String) The unit for the alarm's associated metric. + # Valid values include: + # * +Seconds+ + # * +Microseconds+ + # * +Milliseconds+ + # * +Bytes+ + # * +Kilobytes+ + # * +Megabytes+ + # * +Gigabytes+ + # * +Terabytes+ + # * +Bits+ + # * +Kilobits+ + # * +Megabits+ + # * +Gigabits+ + # * +Terabits+ + # * +Percent+ + # * +Count+ + # * +Bytes/Second+ + # * +Kilobytes/Second+ + # * +Megabytes/Second+ + # * +Gigabytes/Second+ + # * +Terabytes/Second+ + # * +Bits/Second+ + # * +Kilobits/Second+ + # * +Megabits/Second+ + # * +Gigabits/Second+ + # * +Terabits/Second+ + # * +Count/Second+ + # * +None+ + # * +:evaluation_periods+ - *required* - (Integer) The number of + # periods over which data is compared to the specified threshold. + # * +:threshold+ - *required* - (Float) The value against which the + # specified statistic is compared. + # * +:comparison_operator+ - *required* - (String) The arithmetic + # operation to use when comparing the specified Statistic and + # Threshold. The specified Statistic value is used as the first + # operand. Valid values include: + # * +GreaterThanOrEqualToThreshold+ + # * +GreaterThanThreshold+ + # * +LessThanThreshold+ + # * +LessThanOrEqualToThreshold+ # @return [Core::Response] # @!method put_metric_data(options = {}) # Calls the PutMetricData API operation. # @param [Hash] options - # * +:namespace+ - *required* - (String) - # * +:metric_data+ - *required* - (Array) - # * +:metric_name+ - *required* - (String) - # * +:dimensions+ - (Array) - # * +:name+ - *required* - (String) - # * +:value+ - *required* - (String) - # * +:timestamp+ - (String) - # * +:value+ - (Float) - # * +:statistic_values+ - (Hash) - # * +:sample_count+ - *required* - (Float) - # * +:sum+ - *required* - (Float) - # * +:minimum+ - *required* - (Float) - # * +:maximum+ - *required* - (Float) - # * +:unit+ - (String) + # * +:namespace+ - *required* - (String) The namespace for the metric + # data. + # * +:metric_data+ - *required* - (Array) A list of data + # describing the metric. + # * +:metric_name+ - *required* - (String) The name of the metric. + # * +:dimensions+ - (Array) A list of dimensions associated + # with the metric. + # * +:name+ - *required* - (String) The name of the dimension. + # * +:value+ - *required* - (String) The value representing the + # dimension measurement + # * +:timestamp+ - (String) The time stamp used for + # the metric. If not specified, the default value is set to the + # time the metric data was received. + # * +:value+ - (Float) The value for the metric. Although the Value + # parameter accepts numbers of type Double, Amazon CloudWatch + # truncates values with very large exponents. Values with base-10 + # exponents greater than 126 (1 x 10^126) are truncated. Likewise, + # values with base-10 exponents less than -130 (1 x 10^-130) are + # also truncated. + # * +:statistic_values+ - (Hash) A set of statistical values + # describing the metric. + # * +:sample_count+ - *required* - (Float) The number of samples + # used for the statistic set. + # * +:sum+ - *required* - (Float) The sum of values for the sample + # set. + # * +:minimum+ - *required* - (Float) The minimum value of the + # sample set. + # * +:maximum+ - *required* - (Float) The maximum value of the + # sample set. + # * +:unit+ - (String) The unit of the metric. Valid values include: + # * +Seconds+ + # * +Microseconds+ + # * +Milliseconds+ + # * +Bytes+ + # * +Kilobytes+ + # * +Megabytes+ + # * +Gigabytes+ + # * +Terabytes+ + # * +Bits+ + # * +Kilobits+ + # * +Megabits+ + # * +Gigabits+ + # * +Terabits+ + # * +Percent+ + # * +Count+ + # * +Bytes/Second+ + # * +Kilobytes/Second+ + # * +Megabytes/Second+ + # * +Gigabytes/Second+ + # * +Terabytes/Second+ + # * +Bits/Second+ + # * +Kilobits/Second+ + # * +Megabits/Second+ + # * +Gigabits/Second+ + # * +Terabits/Second+ + # * +Count/Second+ + # * +None+ # @return [Core::Response] # @!method set_alarm_state(options = {}) # Calls the SetAlarmState API operation. # @param [Hash] options - # * +:alarm_name+ - *required* - (String) - # * +:state_value+ - *required* - (String) - # * +:state_reason+ - *required* - (String) - # * +:state_reason_data+ - (String) + # * +:alarm_name+ - *required* - (String) The descriptive name for the + # alarm. This name must be unique within the user's AWS account. The + # maximum length is 255 characters. + # * +:state_value+ - *required* - (String) The value of the state. + # Valid values include: + # * +OK+ + # * +ALARM+ + # * +INSUFFICIENT_DATA+ + # * +:state_reason+ - *required* - (String) The reason that this alarm + # is set to this specific state (in human-readable text format) + # * +:state_reason_data+ - (String) The reason that this alarm is set + # to this specific state (in machine-readable JSON format) # @return [Core::Response] # end client methods # From 17fd89009612882132c15b6c91da42749d320a1d Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Thu, 14 Mar 2013 15:40:01 -0700 Subject: [PATCH 0467/1398] Added a rule to ignore enum traits. --- lib/aws/core/xml/grammar.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/aws/core/xml/grammar.rb b/lib/aws/core/xml/grammar.rb index f6e2a5d35e0..46e173d0bc5 100644 --- a/lib/aws/core/xml/grammar.rb +++ b/lib/aws/core/xml/grammar.rb @@ -129,7 +129,7 @@ def validate_config_method(method) allow_methods = %w( rename attribute_name boolean integer long float list force string ignore collect_values symbol_value timestamp map_entry map blob - position + enum position ) unless allow_methods.include?(method.to_s) raise "#{method} cannot be used in configuration" @@ -286,6 +286,7 @@ def eql? other end alias_method :==, :eql? + def enum *args; end def position *args; end def http_trait *args; end alias_method :http_header, :http_trait From e72674d5ec7d1b2f81816e15a1ebc33cd1943446 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Fri, 15 Mar 2013 07:57:30 -0700 Subject: [PATCH 0468/1398] Resolved an issue with the OpsWorks API configuration. One of the input params was incorrectly inflected. --- lib/aws/api_config/OpsWorks-2013-02-18.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/aws/api_config/OpsWorks-2013-02-18.yml b/lib/aws/api_config/OpsWorks-2013-02-18.yml index 6545da204f7..1b18be6da27 100644 --- a/lib/aws/api_config/OpsWorks-2013-02-18.yml +++ b/lib/aws/api_config/OpsWorks-2013-02-18.yml @@ -583,7 +583,7 @@ :sym: :instance_id :type: :string Ec2InstanceId: - :sym: :ec_2_instance_id + :sym: :ec2_instance_id :type: :string Hostname: :sym: :hostname @@ -1098,7 +1098,7 @@ :sym: :volume_id :type: :string Ec2VolumeId: - :sym: :ec_2_volume_id + :sym: :ec2_volume_id :type: :string Name: :sym: :name From c18fac92aa32b78c9d91f6b9a6fed05634ffaf2e Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Fri, 15 Mar 2013 09:03:41 -0700 Subject: [PATCH 0469/1398] Added valid values to the documentation for each service client. --- lib/aws/cloud_formation/client.rb | 5 +- lib/aws/cloud_front/client.rb | 26 +- lib/aws/cloud_search/client.rb | 13 +- lib/aws/data_pipeline/client.rb | 123 ++++++--- lib/aws/dynamo_db/client.rb | 70 +++++- lib/aws/ec2/client.rb | 193 +++++++++++++-- lib/aws/elastic_beanstalk/client.rb | 16 +- lib/aws/elasticache/client.rb | 7 +- lib/aws/emr/client.rb | 28 ++- lib/aws/glacier/client.rb | 5 +- lib/aws/iam/client.rb | 13 +- lib/aws/import_export/client.rb | 8 +- lib/aws/ops_works/client.rb | 70 +++++- lib/aws/rds/client.rb | 15 +- lib/aws/redshift/client.rb | 6 +- lib/aws/route_53/client.rb | 48 +++- lib/aws/simple_email_service/client.rb | 12 +- lib/aws/simple_workflow/client.rb | 70 +++++- lib/aws/storage_gateway/client.rb | 329 +++++++++++++++---------- 19 files changed, 800 insertions(+), 257 deletions(-) diff --git a/lib/aws/cloud_formation/client.rb b/lib/aws/cloud_formation/client.rb index 8eb9b147753..25330eb4598 100644 --- a/lib/aws/cloud_formation/client.rb +++ b/lib/aws/cloud_formation/client.rb @@ -86,7 +86,10 @@ class Client < Core::QueryClient # * +:on_failure+ - (String) Determines what action will be taken if # stack creation fails. This must be one of: DO_NOTHING, ROLLBACK, or # DELETE. You can specify either OnFailure or DisableRollback, but - # not both. Default: ROLLBACK + # not both. Default: ROLLBACK Valid values include: + # * +DO_NOTHING+ + # * +ROLLBACK+ + # * +DELETE+ # * +:tags+ - (Array) A set of user-defined Tags to associate # with this stack, represented by key/value pairs. Tags defined for # the stack are propogated to EC2 resources that are created as part diff --git a/lib/aws/cloud_front/client.rb b/lib/aws/cloud_front/client.rb index 346c4746d97..912068fe283 100644 --- a/lib/aws/cloud_front/client.rb +++ b/lib/aws/cloud_front/client.rb @@ -128,7 +128,10 @@ class Client < Core::RESTXMLClient # * +:https_port+ - *required* - (Integer) The HTTPS port the # custom origin listens on. # * +:origin_protocol_policy+ - *required* - (String) The origin - # protocol policy to apply to your origin. + # protocol policy to apply to your origin. Valid values + # include: + # * +http-only+ + # * +match-viewer+ # * +:default_cache_behavior+ - *required* - (Hash) A complex type that # describes the default cache behavior if you do not specify a # CacheBehavior element or if files don't match any of the values of @@ -173,7 +176,9 @@ class Client < Core::RESTXMLClient # matches the path pattern in PathPattern. If you want CloudFront # to allow end users to use any available protocol, specify # allow-all. If you want CloudFront to require HTTPS, specify - # https. + # https. Valid values include: + # * +allow-all+ + # * +https-only+ # * +:min_ttl+ - *required* - (Integer) The minimum amount of time # that you want objects to stay in CloudFront caches before # CloudFront queries your origin to see whether the object has been @@ -234,7 +239,9 @@ class Client < Core::RESTXMLClient # request matches the path pattern in PathPattern. If you want # CloudFront to allow end users to use any available protocol, # specify allow-all. If you want CloudFront to require HTTPS, - # specify https. + # specify https. Valid values include: + # * +allow-all+ + # * +https-only+ # * +:min_ttl+ - *required* - (Integer) The minimum amount of time # that you want objects to stay in CloudFront caches before # CloudFront queries your origin to see whether the object has @@ -984,7 +991,10 @@ class Client < Core::RESTXMLClient # * +:https_port+ - *required* - (Integer) The HTTPS port the # custom origin listens on. # * +:origin_protocol_policy+ - *required* - (String) The origin - # protocol policy to apply to your origin. + # protocol policy to apply to your origin. Valid values + # include: + # * +http-only+ + # * +match-viewer+ # * +:default_cache_behavior+ - *required* - (Hash) A complex type that # describes the default cache behavior if you do not specify a # CacheBehavior element or if files don't match any of the values of @@ -1029,7 +1039,9 @@ class Client < Core::RESTXMLClient # matches the path pattern in PathPattern. If you want CloudFront # to allow end users to use any available protocol, specify # allow-all. If you want CloudFront to require HTTPS, specify - # https. + # https. Valid values include: + # * +allow-all+ + # * +https-only+ # * +:min_ttl+ - *required* - (Integer) The minimum amount of time # that you want objects to stay in CloudFront caches before # CloudFront queries your origin to see whether the object has been @@ -1090,7 +1102,9 @@ class Client < Core::RESTXMLClient # request matches the path pattern in PathPattern. If you want # CloudFront to allow end users to use any available protocol, # specify allow-all. If you want CloudFront to require HTTPS, - # specify https. + # specify https. Valid values include: + # * +allow-all+ + # * +https-only+ # * +:min_ttl+ - *required* - (Integer) The minimum amount of time # that you want objects to stay in CloudFront caches before # CloudFront queries your origin to see whether the object has diff --git a/lib/aws/cloud_search/client.rb b/lib/aws/cloud_search/client.rb index 9d290e09cb1..2e4f98ef7f2 100644 --- a/lib/aws/cloud_search/client.rb +++ b/lib/aws/cloud_search/client.rb @@ -60,7 +60,11 @@ class Client < Core::QueryClient # cannot be specified as field or rank expression names. # * +:index_field_type+ - *required* - (String) The type of field. # Based on this type, exactly one of the UIntOptions, - # LiteralOptions or TextOptions must be present. + # LiteralOptions or TextOptions must be present. Valid values + # include: + # * +uint+ + # * +literal+ + # * +text+ # * +:u_int_options+ - (Hash) Options for an unsigned integer field. # Present if IndexFieldType specifies the field is of type unsigned # integer. @@ -92,7 +96,10 @@ class Client < Core::QueryClient # configure a maximum of 20 sources for an IndexField. # * +:source_data_function+ - *required* - (String) Identifies the # transformation to apply when copying data from a source - # attribute. + # attribute. Valid values include: + # * +Copy+ + # * +TrimTitle+ + # * +Map+ # * +:source_data_copy+ - (Hash) Copies data from a source document # attribute to an IndexField. # * +:source_name+ - *required* - (String) The name of the @@ -170,7 +177,7 @@ class Client < Core::QueryClient # expressions and supports: Integer, floating point, hex and octal # literals Shortcut evaluation of logical operators such that an # expression a || b evaluates to the value a if a is +true+ without - # evaluating b at all JavaScript order of precedence for operators + # evaluting b at all JavaScript order of precendence for operators # Arithmetic operators: + - * / % Boolean operators (including the # ternary operator) Bitwise operators Comparison operators Common # mathematic functions: abs ceil erf exp floor lgamma ln log2 log10 diff --git a/lib/aws/data_pipeline/client.rb b/lib/aws/data_pipeline/client.rb index f8e4d056535..4bc552dfd5c 100644 --- a/lib/aws/data_pipeline/client.rb +++ b/lib/aws/data_pipeline/client.rb @@ -48,7 +48,8 @@ class Client < Core::JSONClient # exists with the same name and unique identifier, a new pipeline # will not be created. Instead, you'll receive the pipeline # identifier from the previous attempt. The uniqueness of the name - # and unique identifier combination is scoped to the AWS account. + # and unique identifier combination is scoped to the AWS account or + # IAM user credentials. # * +:description+ - (String) The description of the new pipeline. # @return [Core::Response] # The #data method of the response object returns @@ -70,7 +71,10 @@ class Client < Core::JSONClient # * +:object_ids+ - *required* - (Array) Identifiers of the # pipeline objects that contain the definitions to be described. You # can pass as many as 25 identifiers in a single call to - # DescribeObjects + # DescribeObjects. + # * +:evaluate_expressions+ - (Boolean) Indicates whether any + # expressions in the object should be evaluated when the object + # descriptions are returned. # * +:marker+ - (String) The starting point for the results to be # returned. The first time you call DescribeObjects, this value # should be empty. As long as the action returns HasMoreResults as @@ -111,9 +115,10 @@ class Client < Core::JSONClient # @!method evaluate_expression(options = {}) # Calls the EvaluateExpression API operation. # @param [Hash] options - # * +:pipeline_id+ - *required* - (String) - # * +:object_id+ - *required* - (String) - # * +:expression+ - *required* - (String) + # * +:pipeline_id+ - *required* - (String) The identifier of the + # pipeline. + # * +:object_id+ - *required* - (String) The identifier of the object. + # * +:expression+ - *required* - (String) The expression to evaluate. # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -124,7 +129,11 @@ class Client < Core::JSONClient # @param [Hash] options # * +:pipeline_id+ - *required* - (String) The identifier of the # pipeline. - # * +:version+ - (String) + # * +:version+ - (String) The version of the pipeline definition to + # retrieve. This parameter accepts the values latest and active. + # Where latest indicates the last definition saved to the pipeline + # and active indicates the last definition of the pipeline that was + # activated. # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -157,15 +166,22 @@ class Client < Core::JSONClient # Calls the PollForTask API operation. # @param [Hash] options # * +:worker_group+ - *required* - (String) Indicates the type of task - # the worker is configured to accept and process. The worker group is - # set as a field on objects in the pipeline when they are created. - # You can only specify a single value for workerGroup in the call to - # PollForTask. There are no wildcard values permitted in workerGroup, - # the string must be an exact match. - # * +:hostname+ - (String) The public DNS name of the calling worker - # client. + # the task runner is configured to accept and process. The worker + # group is set as a field on objects in the pipeline when they are + # created. You can only specify a single value for workerGroup in the + # call to PollForTask. There are no wildcard values permitted in + # workerGroup, the string must be an exact, case-sensitive, match. + # * +:hostname+ - (String) The public DNS name of the calling task + # runner. # * +:instance_identity+ - (Hash) Identity information for the Amazon - # EC2 instance that is hosting the worker client. + # EC2 instance that is hosting the task runner. You can get this + # value by calling the URI, + # http://169.254.169.254/latest/meta-data/instance-id, from the EC2 + # instance. For more information, go to Instance Metadata in the + # Amazon Elastic Compute Cloud User Guide. Passing in this value + # proves that your task runner is running on an EC2 instance, and + # ensures the proper AWS Data Pipeline service charges are applied to + # your pipeline. # * +:document+ - (String) A description of an Amazon EC2 instance # that is generated when the instance is launched and exposed to # the instance via the instance meta-data service in the form of a @@ -221,28 +237,46 @@ class Client < Core::JSONClient # * +:query+ - (Hash) Query that defines the objects to be returned. # The Query object can contain a maximum of ten selectors. The # conditions in the query are limited to top-level String fields in - # the object. These filters can be applied to both logical, physical, - # and physical attempt objects. + # the object. These filters can be applied to components, instances, + # and attempts. # * +:selectors+ - (Array) List of selectors that define the # query. An object must satisfy all of the selectors to match the # query. # * +:field_name+ - (String) The name of the field that the # operator will be applied to. The field name is the "key" # portion of the field definition in the pipeline definition - # syntax that is used by the AWS Data Pipeline API. + # syntax that is used by the AWS Data Pipeline API. If the field + # is not set on the object, the condition fails. # * +:operator+ - (Hash) # * +:type+ - (String) The logical operation to be performed: # equal (EQ), equal reference (REF_EQ), less than or equal # (LE), greater than or equal (GE), or between (BETWEEN). Equal # reference (REF_EQ) can be used only with reference fields. # The other comparison types can be used only with String - # fields. + # fields. The comparison types you can use apply only to + # certain object fields, as detailed below. The comparison + # operators EQ and REF_EQ act on the following fields: name + # @sphere parent @componentParent @instanceParent @status + # @scheduledStartTime @scheduledEndTime @actualStartTime + # @actualEndTime The comparison operators GE, LE, and BETWEEN + # act on the following fields: @scheduledStartTime + # @scheduledEndTime @actualStartTime @actualEndTime Note that + # fields beginning with the at sign (@) are read-only and set + # by the web service. When you name fields, you should choose + # names containing only alpha-numeric values, as symbols may be + # reserved by AWS Data Pipeline. A best practice for + # user-defined fields that you add to a pipeline is to prefix + # their name with the string "my". Valid values include: + # * +EQ+ + # * +REF_EQ+ + # * +LE+ + # * +GE+ + # * +BETWEEN+ # * +:values+ - (Array) The value that the actual field # value will be compared with. # * +:sphere+ - *required* - (String) Specifies whether the query - # applies to logical objects or physical objects. Allowable values: - # LO (logical object), PO (physical object), POAttempt (physical - # object attempt). + # applies to components or instances. Allowable values: COMPONENT, + # INSTANCE, ATTEMPT. # * +:marker+ - (String) The starting point for the results to be # returned. The first time you call QueryObjects, this value should # be empty. As long as the action returns HasMoreResults as True, you @@ -262,13 +296,36 @@ class Client < Core::JSONClient # Calls the ReportTaskProgress API operation. # @param [Hash] options # * +:task_id+ - *required* - (String) Identifier of the task assigned - # to the worker. This value is provided in the TaskObject that the - # service returns with the response for the PollForTask action. + # to the task runner. This value is provided in the TaskObject that + # the service returns with the response for the PollForTask action. # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: # * +:canceled+ - (Boolean) + # @!method report_task_runner_heartbeat(options = {}) + # Calls the ReportTaskRunnerHeartbeat API operation. + # @param [Hash] options + # * +:taskrunner_id+ - *required* - (String) The identifier of the task + # runner. This value should be unique across your AWS account. In the + # case of AWS Data Pipeline Task Runner launched on a resource + # managed by AWS Data Pipeline, the web service provides a unique + # identifier when it launches the application. If you have written a + # custom task runner, you should assign a unique identifier for the + # task runner. + # * +:worker_group+ - (String) Indicates the type of task the task + # runner is configured to accept and process. The worker group is set + # as a field on objects in the pipeline when they are created. You + # can only specify a single value for workerGroup in the call to + # ReportTaskRunnerHeartbeat. There are no wildcard values permitted + # in workerGroup, the string must be an exact, case-sensitive, match. + # * +:hostname+ - (String) The public DNS name of the calling task + # runner. + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:terminate+ - (Boolean) + # @!method set_status(options = {}) # Calls the SetStatus API operation. # @param [Hash] options @@ -276,21 +333,25 @@ class Client < Core::JSONClient # contains the objects. # * +:object_ids+ - *required* - (Array) Identifies an array of # objects. The corresponding objects can be either physical or - # logical objects, but not a mix of both types. + # components, but not a mix of both types. # * +:status+ - *required* - (String) Specifies the status to be set on - # all the objects in objectIds.member.N. For logical objects, this - # can be either pause or resume. For physical objects, this can be - # either cancel or rerun. + # all the objects in objectIds. For components, this can be either + # PAUSE or RESUME. For instances, this can be either CANCEL, RERUN, + # or MARK_FINISHED. # @return [Core::Response] # @!method set_task_status(options = {}) # Calls the SetTaskStatus API operation. # @param [Hash] options # * +:task_id+ - *required* - (String) Identifies the task assigned to - # the worker. This value is set in the TaskObject that is returned by - # the PollForTask action. - # * +:task_status+ - *required* - (String) If FINISHED, the task was - # Status successfully completed. + # the task runner. This value is set in the TaskObject that is + # returned by the PollForTask action. + # * +:task_status+ - *required* - (String) If FINISHED, the task + # successfully completed. If FAILED the task ended unsuccessfully. + # The FALSE value is used by preconditions. Valid values include: + # * +FINISHED+ + # * +FAILED+ + # * +FALSE+ # * +:error_code+ - (Integer) If an error occurred during the task, # specifies a numerical value that represents the error. This value # is set on the physical attempt object. It is used to display error diff --git a/lib/aws/dynamo_db/client.rb b/lib/aws/dynamo_db/client.rb index 7705bb2f6b1..f1029adad58 100644 --- a/lib/aws/dynamo_db/client.rb +++ b/lib/aws/dynamo_db/client.rb @@ -199,7 +199,11 @@ class Client < Core::JSONClient # * +:attribute_name+ - *required* - (String) The AttributeName of # the KeySchemaElement. # * +:attribute_type+ - *required* - (String) The AttributeType of - # the KeySchemaElement which can be a String or a Number. + # the KeySchemaElement which can be a String or a Number. Valid + # values include: + # * +S+ + # * +N+ + # * +B+ # * +:range_key_element+ - (Hash) A range key element is treated as a # secondary key (used in conjunction with the primary key), and can # be a string or a number, and is only used for hash-and-range @@ -208,7 +212,11 @@ class Client < Core::JSONClient # * +:attribute_name+ - *required* - (String) The AttributeName of # the KeySchemaElement. # * +:attribute_type+ - *required* - (String) The AttributeType of - # the KeySchemaElement which can be a String or a Number. + # the KeySchemaElement which can be a String or a Number. Valid + # values include: + # * +S+ + # * +N+ + # * +B+ # * +:provisioned_throughput+ - *required* - (Hash) # * +:read_capacity_units+ - *required* - (Integer) ReadCapacityUnits # are in terms of strictly consistent reads, assuming items of 1k. @@ -298,7 +306,12 @@ class Client < Core::JSONClient # * +:bs+ - (Array) A set of binary attributes. # * +:exists+ - (Boolean) Specify whether or not a value already # exists for the attribute name-value pair. - # * +:return_values+ - (String) + # * +:return_values+ - (String) Valid values include: + # * +NONE+ + # * +ALL_OLD+ + # * +UPDATED_OLD+ + # * +ALL_NEW+ + # * +UPDATED_NEW+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -471,7 +484,12 @@ class Client < Core::JSONClient # * +:bs+ - (Array) A set of binary attributes. # * +:exists+ - (Boolean) Specify whether or not a value already # exists for the attribute name-value pair. - # * +:return_values+ - (String) + # * +:return_values+ - (String) Valid values include: + # * +NONE+ + # * +ALL_OLD+ + # * +UPDATED_OLD+ + # * +ALL_NEW+ + # * +UPDATED_NEW+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -534,7 +552,21 @@ class Client < Core::JSONClient # * +:ss+ - (Array) A set of strings. # * +:ns+ - (Array) A set of numbers. # * +:bs+ - (Array) A set of binary attributes. - # * +:comparison_operator+ - *required* - (String) + # * +:comparison_operator+ - *required* - (String) Valid values + # include: + # * +EQ+ + # * +NE+ + # * +IN+ + # * +LE+ + # * +LT+ + # * +GE+ + # * +GT+ + # * +BETWEEN+ + # * +NOT_NULL+ + # * +NULL+ + # * +CONTAINS+ + # * +NOT_CONTAINS+ + # * +BEGINS_WITH+ # * +:scan_index_forward+ - (Boolean) Specifies forward or backward # traversal of the index. Amazon DynamoDB returns results reflecting # the requested order, determined by the range key. Default is +true+ @@ -642,7 +674,21 @@ class Client < Core::JSONClient # * +:ss+ - (Array) A set of strings. # * +:ns+ - (Array) A set of numbers. # * +:bs+ - (Array) A set of binary attributes. - # * +:comparison_operator+ - *required* - (String) + # * +:comparison_operator+ - *required* - (String) Valid values + # include: + # * +EQ+ + # * +NE+ + # * +IN+ + # * +LE+ + # * +LT+ + # * +GE+ + # * +GT+ + # * +BETWEEN+ + # * +NOT_NULL+ + # * +NULL+ + # * +CONTAINS+ + # * +NOT_CONTAINS+ + # * +BEGINS_WITH+ # * +:exclusive_start_key+ - (Hash) Primary key of the item from which # to continue an earlier scan. An earlier scan might provide this # value if that scan operation was interrupted before scanning the @@ -765,7 +811,10 @@ class Client < Core::JSONClient # * +:ss+ - (Array) A set of strings. # * +:ns+ - (Array) A set of numbers. # * +:bs+ - (Array) A set of binary attributes. - # * +:action+ - (String) + # * +:action+ - (String) Valid values include: + # * +ADD+ + # * +PUT+ + # * +DELETE+ # * +:expected+ - (Hash) # * +:value+ - (Hash) Specify whether or not a value already exists # and has a specific content for the attribute name-value pair. @@ -783,7 +832,12 @@ class Client < Core::JSONClient # * +:bs+ - (Array) A set of binary attributes. # * +:exists+ - (Boolean) Specify whether or not a value already # exists for the attribute name-value pair. - # * +:return_values+ - (String) + # * +:return_values+ - (String) Valid values include: + # * +NONE+ + # * +ALL_OLD+ + # * +UPDATED_OLD+ + # * +ALL_NEW+ + # * +UPDATED_NEW+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: diff --git a/lib/aws/ec2/client.rb b/lib/aws/ec2/client.rb index b4bce455716..7c29034885a 100644 --- a/lib/aws/ec2/client.rb +++ b/lib/aws/ec2/client.rb @@ -71,7 +71,9 @@ class Client < Core::QueryClient # Calls the AllocateAddress API operation. # @param [Hash] options # * +:domain+ - (String) Set to vpc to allocate the address to your - # VPC. By default, will allocate to EC2. + # VPC. By default, will allocate to EC2. Valid values include: + # * +vpc+ + # * +standard+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -485,7 +487,9 @@ class Client < Core::QueryClient # gigabytes. # * +:delete_on_termination+ - (Boolean) Specifies whether the # Amazon EBS volume is deleted on instance termination. - # * +:volume_type+ - (String) + # * +:volume_type+ - (String) Valid values include: + # * +standard+ + # * +io1+ # * +:iops+ - (Integer) # * +:no_device+ - (String) Specifies the device name to suppress # during instance launch. @@ -499,10 +503,15 @@ class Client < Core::QueryClient # @param [Hash] options # * +:description+ - (String) # * +:instance_id+ - *required* - (String) - # * +:target_environment+ - (String) + # * +:target_environment+ - (String) Valid values include: + # * +citrix+ + # * +vmware+ # * +:export_to_s3_task+ - (Hash) - # * +:disk_image_format+ - (String) - # * +:container_format+ - (String) + # * +:disk_image_format+ - (String) Valid values include: + # * +vmdk+ + # * +vhd+ + # * +:container_format+ - (String) Valid values include: + # * +ova+ # * +:s3_bucket+ - (String) # * +:s3_prefix+ - (String) # @return [Core::Response] @@ -593,7 +602,9 @@ class Client < Core::QueryClient # * +:protocol+ - *required* - (String) IP protocol the rule applies # to. Valid Values: tcp, udp, icmp or an IP protocol number. # * +:rule_action+ - *required* - (String) Whether to allow or deny - # traffic that matches the rule. + # traffic that matches the rule. Valid values include: + # * +allow+ + # * +deny+ # * +:egress+ - *required* - (Boolean) Whether this rule applies to # egress traffic from the subnet ( +true+ ) or ingress traffic to the # subnet ( +false+ ). @@ -676,6 +687,8 @@ class Client < Core::QueryClient # * +:group_name+ - *required* - (String) The name of the # PlacementGroup. # * +:strategy+ - *required* - (String) The PlacementGroup strategy. + # Valid values include: + # * +cluster+ # @return [Core::Response] # @!method create_reserved_instances_listing(options = {}) @@ -856,7 +869,9 @@ class Client < Core::QueryClient # create the new volume. # * +:availability_zone+ - *required* - (String) The Availability Zone # in which to create the new volume. - # * +:volume_type+ - (String) + # * +:volume_type+ - (String) Valid values include: + # * +standard+ + # * +io1+ # * +:iops+ - (Integer) # @return [Core::Response] # The #data method of the response object returns @@ -1450,7 +1465,19 @@ class Client < Core::QueryClient # * +:attribute+ - *required* - (String) The name of the attribute to # describe. Available attribute names: instanceType, kernel, ramdisk, # userData, disableApiTermination, instanceInitiatedShutdownBehavior, - # rootDeviceName, blockDeviceMapping + # rootDeviceName, blockDeviceMapping Valid values include: + # * +instanceType+ + # * +kernel+ + # * +ramdisk+ + # * +userData+ + # * +disableApiTermination+ + # * +instanceInitiatedShutdownBehavior+ + # * +rootDeviceName+ + # * +blockDeviceMapping+ + # * +productCodes+ + # * +sourceDestCheck+ + # * +groupSet+ + # * +ebsOptimized+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -1940,7 +1967,24 @@ class Client < Core::QueryClient # list of the unique IDs of the Reserved Instance offerings to # describe. # * +:instance_type+ - (String) The instance type on which the Reserved - # Instance can be used. + # Instance can be used. Valid values include: + # * +t1.micro+ + # * +m1.small+ + # * +m1.medium+ + # * +m1.large+ + # * +m1.xlarge+ + # * +m2.xlarge+ + # * +m2.2xlarge+ + # * +m2.4xlarge+ + # * +m3.xlarge+ + # * +m3.2xlarge+ + # * +c1.medium+ + # * +c1.xlarge+ + # * +hi1.4xlarge+ + # * +hs1.8xlarge+ + # * +cc1.4xlarge+ + # * +cc2.8xlarge+ + # * +cg1.4xlarge+ # * +:availability_zone+ - (String) The Availability Zone in which the # Reserved Instance can be used. # * +:product_description+ - (String) The Reserved Instance product @@ -2069,6 +2113,9 @@ class Client < Core::QueryClient # whose attribute is being described. # * +:attribute+ - *required* - (String) The name of the EBS attribute # to describe. Available attribute names: createVolumePermission + # Valid values include: + # * +productCodes+ + # * +createVolumePermission+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -2289,7 +2336,9 @@ class Client < Core::QueryClient # Calls the DescribeVolumeAttribute API operation. # @param [Hash] options # * +:volume_id+ - *required* - (String) - # * +:attribute+ - (String) + # * +:attribute+ - (String) Valid values include: + # * +autoEnableIO+ + # * +productCodes+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -2372,7 +2421,9 @@ class Client < Core::QueryClient # Calls the DescribeVpcAttribute API operation. # @param [Hash] options # * +:vpc_id+ - *required* - (String) - # * +:attribute+ - (String) + # * +:attribute+ - (String) Valid values include: + # * +enableDnsSupport+ + # * +enableDnsHostnames+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -2595,7 +2646,24 @@ class Client < Core::QueryClient # * +:security_groups+ - (Array) # * +:additional_info+ - (String) # * +:user_data+ - (String) - # * +:instance_type+ - (String) + # * +:instance_type+ - (String) Valid values include: + # * +t1.micro+ + # * +m1.small+ + # * +m1.medium+ + # * +m1.large+ + # * +m1.xlarge+ + # * +m2.xlarge+ + # * +m2.2xlarge+ + # * +m2.4xlarge+ + # * +m3.xlarge+ + # * +m3.2xlarge+ + # * +c1.medium+ + # * +c1.xlarge+ + # * +hi1.4xlarge+ + # * +hs1.8xlarge+ + # * +cc1.4xlarge+ + # * +cc2.8xlarge+ + # * +cg1.4xlarge+ # * +:placement+ - (Hash) # * +:availability_zone+ - (String) The availability zone in which # an Amazon EC2 instance runs. @@ -2621,7 +2689,9 @@ class Client < Core::QueryClient # gigabytes. # * +:delete_on_termination+ - (Boolean) Specifies whether the # Amazon EBS volume is deleted on instance termination. - # * +:volume_type+ - (String) + # * +:volume_type+ - (String) Valid values include: + # * +standard+ + # * +io1+ # * +:iops+ - (Integer) # * +:no_device+ - (String) Specifies the device name to suppress # during instance launch. @@ -2792,7 +2862,19 @@ class Client < Core::QueryClient # * +:attribute+ - (String) The name of the attribute being modified. # Available attribute names: instanceType, kernel, ramdisk, userData, # disableApiTermination, instanceInitiatedShutdownBehavior, - # rootDevice, blockDeviceMapping + # rootDevice, blockDeviceMapping Valid values include: + # * +instanceType+ + # * +kernel+ + # * +ramdisk+ + # * +userData+ + # * +disableApiTermination+ + # * +instanceInitiatedShutdownBehavior+ + # * +rootDeviceName+ + # * +blockDeviceMapping+ + # * +productCodes+ + # * +sourceDestCheck+ + # * +groupSet+ + # * +ebsOptimized+ # * +:value+ - (String) The new value of the instance attribute being # modified. Only valid when kernel, ramdisk, userData, # disableApiTermination or instanceInitiateShutdownBehavior is @@ -2853,7 +2935,10 @@ class Client < Core::QueryClient # * +:snapshot_id+ - *required* - (String) The ID of the EBS snapshot # whose attributes are being modified. # * +:attribute+ - (String) The name of the attribute being modified. - # Available attribute names: createVolumePermission + # Available attribute names: createVolumePermission Valid values + # include: + # * +productCodes+ + # * +createVolumePermission+ # * +:operation_type+ - (String) The operation to perform on the # attribute. Available operation names: add, remove # * +:user_ids+ - (Array) The AWS user IDs to add to or remove @@ -2963,7 +3048,9 @@ class Client < Core::QueryClient # gigabytes. # * +:delete_on_termination+ - (Boolean) Specifies whether the # Amazon EBS volume is deleted on instance termination. - # * +:volume_type+ - (String) + # * +:volume_type+ - (String) Valid values include: + # * +standard+ + # * +io1+ # * +:iops+ - (Integer) # * +:no_device+ - (String) Specifies the device name to suppress # during instance launch. @@ -3004,7 +3091,9 @@ class Client < Core::QueryClient # * +:protocol+ - *required* - (String) IP protocol the rule applies # to. Valid Values: tcp, udp, icmp or an IP protocol number. # * +:rule_action+ - *required* - (String) Whether to allow or deny - # traffic that matches the rule. + # traffic that matches the rule. Valid values include: + # * +allow+ + # * +deny+ # * +:egress+ - *required* - (Boolean) Whether this rule applies to # egress traffic from the subnet ( +true+ ) or ingress traffic ( # +false+ ). @@ -3070,7 +3159,10 @@ class Client < Core::QueryClient # price for any Spot Instance launched to fulfill the request. # * +:instance_count+ - (Integer) Specifies the maximum number of Spot # Instances to launch. - # * +:type+ - (String) Specifies the Spot Instance type. + # * +:type+ - (String) Specifies the Spot Instance type. Valid values + # include: + # * +one-time+ + # * +persistent+ # * +:valid_from+ - (String) Defines the start date # of the request. If this is a one-time request, the request becomes # active at this date and time and remains active until all instances @@ -3098,7 +3190,25 @@ class Client < Core::QueryClient # collectively comprise the launch request have access to this # data. User data is never returned through API responses. # * +:addressing_type+ - (String) Deprecated. - # * +:instance_type+ - (String) Specifies the instance type. + # * +:instance_type+ - (String) Specifies the instance type. Valid + # values include: + # * +t1.micro+ + # * +m1.small+ + # * +m1.medium+ + # * +m1.large+ + # * +m1.xlarge+ + # * +m2.xlarge+ + # * +m2.2xlarge+ + # * +m2.4xlarge+ + # * +m3.xlarge+ + # * +m3.2xlarge+ + # * +c1.medium+ + # * +c1.xlarge+ + # * +hi1.4xlarge+ + # * +hs1.8xlarge+ + # * +cc1.4xlarge+ + # * +cc2.8xlarge+ + # * +cg1.4xlarge+ # * +:placement+ - (Hash) Defines a placement item. # * +:availability_zone+ - (String) The availability zone in which # an Amazon EC2 instance runs. @@ -3125,7 +3235,9 @@ class Client < Core::QueryClient # gigabytes. # * +:delete_on_termination+ - (Boolean) Specifies whether the # Amazon EBS volume is deleted on instance termination. - # * +:volume_type+ - (String) + # * +:volume_type+ - (String) Valid values include: + # * +standard+ + # * +io1+ # * +:iops+ - (Integer) # * +:no_device+ - (String) Specifies the device name to suppress # during instance launch. @@ -3235,7 +3347,20 @@ class Client < Core::QueryClient # * +:instance_id+ - *required* - (String) The ID of the Amazon EC2 # instance whose attribute is being reset. # * +:attribute+ - *required* - (String) The name of the attribute - # being reset. Available attribute names: kernel, ramdisk + # being reset. Available attribute names: kernel, ramdisk Valid + # values include: + # * +instanceType+ + # * +kernel+ + # * +ramdisk+ + # * +userData+ + # * +disableApiTermination+ + # * +instanceInitiatedShutdownBehavior+ + # * +rootDeviceName+ + # * +blockDeviceMapping+ + # * +productCodes+ + # * +sourceDestCheck+ + # * +groupSet+ + # * +ebsOptimized+ # @return [Core::Response] # @!method reset_network_interface_attribute(options = {}) @@ -3252,6 +3377,9 @@ class Client < Core::QueryClient # attribute is being reset. # * +:attribute+ - *required* - (String) The name of the attribute # being reset. Available attribute names: createVolumePermission + # Valid values include: + # * +productCodes+ + # * +createVolumePermission+ # @return [Core::Response] # @!method revoke_security_group_egress(options = {}) @@ -3355,7 +3483,24 @@ class Client < Core::QueryClient # * +:user_data+ - (String) Specifies additional information to make # available to the instance(s). # * +:instance_type+ - (String) Specifies the instance type for the - # launched instances. + # launched instances. Valid values include: + # * +t1.micro+ + # * +m1.small+ + # * +m1.medium+ + # * +m1.large+ + # * +m1.xlarge+ + # * +m2.xlarge+ + # * +m2.2xlarge+ + # * +m2.4xlarge+ + # * +m3.xlarge+ + # * +m3.2xlarge+ + # * +c1.medium+ + # * +c1.xlarge+ + # * +hi1.4xlarge+ + # * +hs1.8xlarge+ + # * +cc1.4xlarge+ + # * +cc2.8xlarge+ + # * +cg1.4xlarge+ # * +:placement+ - (Hash) Specifies the placement constraints # (Availability Zones) for launching the instances. # * +:availability_zone+ - (String) The availability zone in which an @@ -3390,7 +3535,9 @@ class Client < Core::QueryClient # gigabytes. # * +:delete_on_termination+ - (Boolean) Specifies whether the # Amazon EBS volume is deleted on instance termination. - # * +:volume_type+ - (String) + # * +:volume_type+ - (String) Valid values include: + # * +standard+ + # * +io1+ # * +:iops+ - (Integer) # * +:no_device+ - (String) Specifies the device name to suppress # during instance launch. diff --git a/lib/aws/elastic_beanstalk/client.rb b/lib/aws/elastic_beanstalk/client.rb index e4735847ad2..a79eb413536 100644 --- a/lib/aws/elastic_beanstalk/client.rb +++ b/lib/aws/elastic_beanstalk/client.rb @@ -504,7 +504,13 @@ class Client < Core::QueryClient # with this request ID. # * +:severity+ - (String) If specified, limits the events returned # from this call to include only those with the specified severity or - # higher. + # higher. Valid values include: + # * +TRACE+ + # * +DEBUG+ + # * +INFO+ + # * +WARN+ + # * +ERROR+ + # * +FATAL+ # * +:start_time+ - (String) If specified, AWS # Elastic Beanstalk restricts the returned descriptions to those that # occur on or after this time. @@ -569,7 +575,8 @@ class Client < Core::QueryClient # both. If you do not specify either, AWS Elastic Beanstalk returns # MissingRequiredParameter error. # * +:info_type+ - *required* - (String) The type of information to - # request. + # request. Valid values include: + # * +tail+ # @return [Core::Response] # @!method restart_app_server(options = {}) @@ -599,13 +606,14 @@ class Client < Core::QueryClient # or both. If you do not specify either, AWS Elastic Beanstalk # returns MissingRequiredParameter error. # * +:info_type+ - *required* - (String) The type of information to - # retrieve. + # retrieve. Valid values include: + # * +tail+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: # * +:environment_info+ - (Array) # * +:info_type+ - (String) - # * +:ec_2_instance_id+ - (String) + # * +:ec2_instance_id+ - (String) # * +:sample_timestamp+ - (Time) # * +:message+ - (String) diff --git a/lib/aws/elasticache/client.rb b/lib/aws/elasticache/client.rb index cc307731118..b2ef66b504c 100644 --- a/lib/aws/elasticache/client.rb +++ b/lib/aws/elasticache/client.rb @@ -533,7 +533,12 @@ class Client < Core::QueryClient # for which events will be returned. If not specified, then all # sources are included in the response. # * +:source_type+ - (String) The event source to retrieve events for. - # If no value is specified, all events are returned. + # If no value is specified, all events are returned. Valid values + # include: + # * +cache-cluster+ + # * +cache-parameter-group+ + # * +cache-security-group+ + # * +cache-subnet-group+ # * +:start_time+ - (String) The beginning of the # time interval to retrieve events for, specified in ISO 8601 format. # * +:end_time+ - (String) The end of the time diff --git a/lib/aws/emr/client.rb b/lib/aws/emr/client.rb index 3d02732f69e..a9cedfc0e42 100644 --- a/lib/aws/emr/client.rb +++ b/lib/aws/emr/client.rb @@ -29,9 +29,14 @@ class Client < Core::QueryClient # add. # * +:name+ - (String) Friendly name given to the instance group. # * +:market+ - (String) Market type of the Amazon EC2 instances used - # to create a cluster node. + # to create a cluster node. Valid values include: + # * +ON_DEMAND+ + # * +SPOT+ # * +:instance_role+ - *required* - (String) The role of the instance - # group in the cluster. + # group in the cluster. Valid values include: + # * +MASTER+ + # * +CORE+ + # * +TASK+ # * +:bid_price+ - (String) Bid price for each Amazon EC2 instance in # the instance group when launching nodes as Spot Instances, # expressed in USD. @@ -57,7 +62,10 @@ class Client < Core::QueryClient # executed by the job flow. # * +:name+ - *required* - (String) The name of the job flow step. # * +:action_on_failure+ - (String) Specifies the action to take if - # the job flow step fails. + # the job flow step fails. Valid values include: + # * +TERMINATE_JOB_FLOW+ + # * +CANCEL_AND_WAIT+ + # * +CONTINUE+ # * +:hadoop_jar_step+ - *required* - (Hash) Specifies the JAR file # used for the job flow step. # * +:properties+ - (Array) A list of Java properties that @@ -199,9 +207,14 @@ class Client < Core::QueryClient # flow's instance groups. # * +:name+ - (String) Friendly name given to the instance group. # * +:market+ - (String) Market type of the Amazon EC2 instances - # used to create a cluster node. + # used to create a cluster node. Valid values include: + # * +ON_DEMAND+ + # * +SPOT+ # * +:instance_role+ - *required* - (String) The role of the - # instance group in the cluster. + # instance group in the cluster. Valid values include: + # * +MASTER+ + # * +CORE+ + # * +TASK+ # * +:bid_price+ - (String) Bid price for each Amazon EC2 instance # in the instance group when launching nodes as Spot Instances, # expressed in USD. @@ -240,7 +253,10 @@ class Client < Core::QueryClient # flow. # * +:name+ - *required* - (String) The name of the job flow step. # * +:action_on_failure+ - (String) Specifies the action to take if - # the job flow step fails. + # the job flow step fails. Valid values include: + # * +TERMINATE_JOB_FLOW+ + # * +CANCEL_AND_WAIT+ + # * +CONTINUE+ # * +:hadoop_jar_step+ - *required* - (Hash) Specifies the JAR file # used for the job flow step. # * +:properties+ - (Array) A list of Java properties that diff --git a/lib/aws/glacier/client.rb b/lib/aws/glacier/client.rb index 5f273058168..02587288742 100644 --- a/lib/aws/glacier/client.rb +++ b/lib/aws/glacier/client.rb @@ -132,9 +132,8 @@ class Client < Core::RESTJSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +:vault_notification_config+ - (Hash) - # * +:sns_topic+ - (String) - # * +:events+ - (Array) + # * +:sns_topic+ - (String) + # * +:events+ - (Array) # @!method initiate_job(options = {}) # Calls the POST InitiateJob API operation. diff --git a/lib/aws/iam/client.rb b/lib/aws/iam/client.rb index 940fc646f8f..848af509cd0 100644 --- a/lib/aws/iam/client.rb +++ b/lib/aws/iam/client.rb @@ -871,7 +871,10 @@ class Client < Core::QueryClient # * +:assignment_status+ - (String) The status (unassigned or assigned) # of the devices to list. If you do not specify an AssignmentStatus, # the action defaults to Any which lists both assigned and unassigned - # virtual MFA devices. + # virtual MFA devices. Valid values include: + # * +Assigned+ + # * +Unassigned+ + # * +Any+ # * +:marker+ - (String) Use this parameter only when paginating # results, and only in a subsequent request after you've received a # response where the results are truncated. Set it to the value of @@ -961,7 +964,10 @@ class Client < Core::QueryClient # Secret Access Key you want to update. # * +:status+ - *required* - (String) The status you want to assign to # the Secret Access Key. Active means the key can be used for API - # calls to AWS, while Inactive means the key cannot be used. + # calls to AWS, while Inactive means the key cannot be used. Valid + # values include: + # * +Active+ + # * +Inactive+ # @return [Core::Response] # @!method update_account_password_policy(options = {}) @@ -1026,6 +1032,9 @@ class Client < Core::QueryClient # * +:status+ - *required* - (String) The status you want to assign to # the certificate. Active means the certificate can be used for API # calls to AWS, while Inactive means the certificate cannot be used. + # Valid values include: + # * +Active+ + # * +Inactive+ # @return [Core::Response] # @!method update_user(options = {}) diff --git a/lib/aws/import_export/client.rb b/lib/aws/import_export/client.rb index 653c76c9e72..30679455e29 100644 --- a/lib/aws/import_export/client.rb +++ b/lib/aws/import_export/client.rb @@ -34,7 +34,9 @@ class Client < Core::QueryClient # @!method create_job(options = {}) # Calls the CreateJob API operation. # @param [Hash] options - # * +:job_type+ - *required* - (String) + # * +:job_type+ - *required* - (String) Valid values include: + # * +Import+ + # * +Export+ # * +:manifest+ - *required* - (String) # * +:manifest_addendum+ - (String) # * +:validate_only+ - *required* - (Boolean) @@ -92,7 +94,9 @@ class Client < Core::QueryClient # @param [Hash] options # * +:job_id+ - *required* - (String) # * +:manifest+ - *required* - (String) - # * +:job_type+ - *required* - (String) + # * +:job_type+ - *required* - (String) Valid values include: + # * +Import+ + # * +Export+ # * +:validate_only+ - *required* - (Boolean) # @return [Core::Response] # The #data method of the response object returns diff --git a/lib/aws/ops_works/client.rb b/lib/aws/ops_works/client.rb index 31a50583477..c3a77b3a031 100644 --- a/lib/aws/ops_works/client.rb +++ b/lib/aws/ops_works/client.rb @@ -37,7 +37,11 @@ class Client < Core::JSONClient # * +:custom_json+ - (String) # * +:use_custom_cookbooks+ - (Boolean) # * +:custom_cookbooks_source+ - (Hash) - # * +:type+ - (String) + # * +:type+ - (String) Valid values include: + # * +git+ + # * +svn+ + # * +archive+ + # * +s3+ # * +:url+ - (String) # * +:username+ - (String) # * +:password+ - (String) @@ -57,9 +61,18 @@ class Client < Core::JSONClient # * +:stack_id+ - *required* - (String) # * +:name+ - *required* - (String) # * +:description+ - (String) - # * +:type+ - *required* - (String) + # * +:type+ - *required* - (String) Valid values include: + # * +rails+ + # * +php+ + # * +nodejs+ + # * +static+ + # * +other+ # * +:app_source+ - (Hash) - # * +:type+ - (String) + # * +:type+ - (String) Valid values include: + # * +git+ + # * +svn+ + # * +archive+ + # * +s3+ # * +:url+ - (String) # * +:username+ - (String) # * +:password+ - (String) @@ -84,7 +97,17 @@ class Client < Core::JSONClient # * +:app_id+ - (String) # * +:instance_ids+ - (Array) # * +:command+ - *required* - (Hash) - # * +:name+ - *required* - (String) + # * +:name+ - *required* - (String) Valid values include: + # * +install_dependencies+ + # * +update_dependencies+ + # * +update_custom_cookbooks+ + # * +execute_recipes+ + # * +deploy+ + # * +rollback+ + # * +start+ + # * +stop+ + # * +restart+ + # * +undeploy+ # * +:args+ - (Hash>) # * +:comment+ - (String) # * +:custom_json+ - (String) @@ -99,7 +122,10 @@ class Client < Core::JSONClient # * +:stack_id+ - *required* - (String) # * +:layer_ids+ - *required* - (Array) # * +:instance_type+ - *required* - (String) - # * +:auto_scaling_type+ - (String) + # * +:auto_scaling_type+ - (String) Valid values include: + # * +AlwaysRunning+ + # * +TimeBasedAutoScaling+ + # * +LoadBasedAutoScaling+ # * +:hostname+ - (String) # * +:os+ - (String) # * +:ssh_key_name+ - (String) @@ -152,7 +178,11 @@ class Client < Core::JSONClient # * +:custom_json+ - (String) # * +:use_custom_cookbooks+ - (Boolean) # * +:custom_cookbooks_source+ - (Hash) - # * +:type+ - (String) + # * +:type+ - (String) Valid values include: + # * +git+ + # * +svn+ + # * +archive+ + # * +s3+ # * +:url+ - (String) # * +:username+ - (String) # * +:password+ - (String) @@ -309,7 +339,7 @@ class Client < Core::JSONClient # a hash with the following structure: # * +:instances+ - (Array) # * +:instance_id+ - (String) - # * +:ec_2_instance_id+ - (String) + # * +:ec2_instance_id+ - (String) # * +:hostname+ - (String) # * +:stack_id+ - (String) # * +:layer_ids+ - (Array) @@ -518,7 +548,7 @@ class Client < Core::JSONClient # a hash with the following structure: # * +:volumes+ - (Array) # * +:volume_id+ - (String) - # * +:ec_2_volume_id+ - (String) + # * +:ec2_volume_id+ - (String) # * +:name+ - (String) # * +:raid_array_id+ - (String) # * +:instance_id+ - (String) @@ -619,9 +649,18 @@ class Client < Core::JSONClient # * +:app_id+ - *required* - (String) # * +:name+ - (String) # * +:description+ - (String) - # * +:type+ - (String) + # * +:type+ - (String) Valid values include: + # * +rails+ + # * +php+ + # * +nodejs+ + # * +static+ + # * +other+ # * +:app_source+ - (Hash) - # * +:type+ - (String) + # * +:type+ - (String) Valid values include: + # * +git+ + # * +svn+ + # * +archive+ + # * +s3+ # * +:url+ - (String) # * +:username+ - (String) # * +:password+ - (String) @@ -642,7 +681,10 @@ class Client < Core::JSONClient # * +:instance_id+ - *required* - (String) # * +:layer_ids+ - (Array) # * +:instance_type+ - (String) - # * +:auto_scaling_type+ - (String) + # * +:auto_scaling_type+ - (String) Valid values include: + # * +AlwaysRunning+ + # * +TimeBasedAutoScaling+ + # * +LoadBasedAutoScaling+ # * +:hostname+ - (String) # * +:os+ - (String) # * +:ssh_key_name+ - (String) @@ -687,7 +729,11 @@ class Client < Core::JSONClient # * +:custom_json+ - (String) # * +:use_custom_cookbooks+ - (Boolean) # * +:custom_cookbooks_source+ - (Hash) - # * +:type+ - (String) + # * +:type+ - (String) Valid values include: + # * +git+ + # * +svn+ + # * +archive+ + # * +s3+ # * +:url+ - (String) # * +:username+ - (String) # * +:password+ - (String) diff --git a/lib/aws/rds/client.rb b/lib/aws/rds/client.rb index 04c5eb823e5..5b484956a8d 100644 --- a/lib/aws/rds/client.rb +++ b/lib/aws/rds/client.rb @@ -1101,7 +1101,12 @@ class Client < Core::QueryClient # for which events will be returned. If not specified, then all # sources are included in the response. # * +:source_type+ - (String) The event source to retrieve events for. - # If no value is specified, all events are returned. + # If no value is specified, all events are returned. Valid values + # include: + # * +db-instance+ + # * +db-parameter-group+ + # * +db-security-group+ + # * +db-snapshot+ # * +:start_time+ - (String) The beginning of the # time interval to retrieve events for, specified in ISO 8601 format. # * +:end_time+ - (String) The end of the time @@ -1571,7 +1576,9 @@ class Client < Core::QueryClient # * +:minimum_engine_version+ - (String) The earliest engine version # to which the parameter can apply. # * +:apply_method+ - (String) Indicates when to apply parameter - # updates. + # updates. Valid values include: + # * +immediate+ + # * +pending-reboot+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -1956,7 +1963,9 @@ class Client < Core::QueryClient # * +:minimum_engine_version+ - (String) The earliest engine version # to which the parameter can apply. # * +:apply_method+ - (String) Indicates when to apply parameter - # updates. + # updates. Valid values include: + # * +immediate+ + # * +pending-reboot+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: diff --git a/lib/aws/redshift/client.rb b/lib/aws/redshift/client.rb index 7a6477471a6..5b1c04f01f5 100644 --- a/lib/aws/redshift/client.rb +++ b/lib/aws/redshift/client.rb @@ -749,7 +749,11 @@ class Client < Core::QueryClient # security group name. Specify cluster-parameter-group when # SourceIdentifier is a cluster parameter group name. Specify # cluster-snapshot when SourceIdentifier is a cluster snapshot - # identifier. + # identifier. Valid values include: + # * +cluster+ + # * +cluster-parameter-group+ + # * +cluster-security-group+ + # * +cluster-snapshot+ # * +:start_time+ - (String) The beginning of the # time interval to retrieve events for, specified in ISO 8601 format. # For more information about ISO 8601, go to the ISO8601 Wikipedia diff --git a/lib/aws/route_53/client.rb b/lib/aws/route_53/client.rb index 21b46ec1e4c..98d9e8a274c 100644 --- a/lib/aws/route_53/client.rb +++ b/lib/aws/route_53/client.rb @@ -40,13 +40,25 @@ class Client < Core::RESTXMLClient # contains one Change element for each resource record set that you # want to create or delete. # * +:action+ - *required* - (String) The action to perform. Valid - # values: CREATE | DELETE + # values: CREATE | DELETE Valid values include: + # * +CREATE+ + # * +DELETE+ # * +:resource_record_set+ - *required* - (Hash) Information about # the resource record set to create or delete. # * +:name+ - *required* - (String) The domain name of the current # resource record set. # * +:type+ - *required* - (String) The type of the current - # resource record set. + # resource record set. Valid values include: + # * +SOA+ + # * +A+ + # * +TXT+ + # * +NS+ + # * +CNAME+ + # * +MX+ + # * +PTR+ + # * +SRV+ + # * +SPF+ + # * +AAAA+ # * +:set_identifier+ - (String) Weighted resource record sets # only: An identifier that differentiates among multiple resource # record sets that have the same combination of DNS name and @@ -59,7 +71,15 @@ class Client < Core::RESTXMLClient # * +:region+ - (String) Regional resource record sets only: Among # resource record sets that have the same combination of DNS name # and type, a value that specifies the AWS region for the current - # resource record set. + # resource record set. Valid values include: + # * +us-east-1+ + # * +us-west-1+ + # * +us-west-2+ + # * +eu-west-1+ + # * +ap-southeast-1+ + # * +ap-southeast-2+ + # * +ap-northeast-1+ + # * +sa-east-1+ # * +:failover+ - (String) Failover resource record sets only: # Among resource record sets that have the same combination of # DNS name and type, a value that indicates whether the current @@ -76,7 +96,10 @@ class Client < Core::RESTXMLClient # record set will be returned if: (1) the primary is failing a # health check and either the secondary is passing a health check # or has no associated health check, or (2) there is no primary - # resource record set. Valid values: PRIMARY | SECONDARY + # resource record set. Valid values: PRIMARY | SECONDARY Valid + # values include: + # * +PRIMARY+ + # * +SECONDARY+ # * +:ttl+ - (Integer) The cache time to live for the current # resource record set. # * +:resource_records+ - (Array) A complex type that @@ -145,7 +168,10 @@ class Client < Core::RESTXMLClient # instance to health check. For HTTP this defaults to 80 if the port # is not specified. # * +:type+ - *required* - (String) The type of health check to be - # performed. Currently supported protocols are TCP and HTTP. + # performed. Currently supported protocols are TCP and HTTP. Valid + # values include: + # * +HTTP+ + # * +TCP+ # * +:resource_path+ - (String) Path to ping on the instance to check # the health. Required only for HTTP health checks, HTTP request is # issued to the instance on the given port and path. @@ -345,7 +371,17 @@ class Client < Core::RESTXMLClient # | NS | PTR | SOA | SPF | SRV | TXT Values for Weighted Resource # Record Sets: A | AAAA | CNAME | TXT Values for Alias Resource Record # Sets: A | AAAA Constraint: Specifying type without specifying name - # returns an InvalidInput error. + # returns an InvalidInput error. Valid values include: + # * +SOA+ + # * +A+ + # * +TXT+ + # * +NS+ + # * +CNAME+ + # * +MX+ + # * +PTR+ + # * +SRV+ + # * +SPF+ + # * +AAAA+ # * +:start_record_identifier+ - (String) Weighted resource record sets # only: If results were truncated for a given DNS name and type, # specify the value of diff --git a/lib/aws/simple_email_service/client.rb b/lib/aws/simple_email_service/client.rb index 806a5b95342..14381be2efd 100644 --- a/lib/aws/simple_email_service/client.rb +++ b/lib/aws/simple_email_service/client.rb @@ -103,7 +103,10 @@ class Client < Core::QueryClient # @param [Hash] options # * +:identity_type+ - (String) The type of the identities to list. # Possible values are "EmailAddress" and "Domain". If this parameter - # is omitted, then all identities will be listed. + # is omitted, then all identities will be listed. Valid values + # include: + # * +EmailAddress+ + # * +Domain+ # * +:next_token+ - (String) The token to use for pagination. # * +:max_items+ - (Integer) The maximum number of identities per page. # Possible values are 1-100 inclusive. @@ -227,10 +230,13 @@ class Client < Core::QueryClient # * +:identity+ - *required* - (String) The identity for which the # topic will be set. Examples: user@example.com, example.com. # * +:notification_type+ - *required* - (String) The type of feedback - # notifications that will be published to the specified topic. + # notifications that will be published to the specified topic. Valid + # values include: + # * +Bounce+ + # * +Complaint+ # * +:sns_topic+ - (String) The Amazon Resource Name (ARN) of the # Amazon Simple Notification Service (Amazon SNS) topic. If the - # parameter is omitted from the request or a null value is passed, + # parameter is ommited from the request or a null value is passed, # the topic is cleared and publishing is disabled. # @return [Core::Response] diff --git a/lib/aws/simple_workflow/client.rb b/lib/aws/simple_workflow/client.rb index 32ec5c2ee77..41e3051bbf8 100644 --- a/lib/aws/simple_workflow/client.rb +++ b/lib/aws/simple_workflow/client.rb @@ -56,7 +56,13 @@ class Client < Core::JSONClient # * +:tag_filter+ - (Hash) # * +:tag+ - *required* - (String) # * +:close_status_filter+ - (Hash) - # * +:status+ - *required* - (String) + # * +:status+ - *required* - (String) Valid values include: + # * +COMPLETED+ + # * +FAILED+ + # * +CANCELED+ + # * +TERMINATED+ + # * +CONTINUED_AS_NEW+ + # * +TIMED_OUT+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -535,7 +541,10 @@ class Client < Core::JSONClient # @param [Hash] options # * +:domain+ - *required* - (String) # * +:name+ - (String) - # * +:registration_status+ - *required* - (String) + # * +:registration_status+ - *required* - (String) Valid values + # include: + # * +REGISTERED+ + # * +DEPRECATED+ # * +:next_page_token+ - (String) # * +:maximum_page_size+ - (Integer) # * +:reverse_order+ - (Boolean) @@ -565,7 +574,13 @@ class Client < Core::JSONClient # * +:execution_filter+ - (Hash) # * +:workflow_id+ - *required* - (String) # * +:close_status_filter+ - (Hash) - # * +:status+ - *required* - (String) + # * +:status+ - *required* - (String) Valid values include: + # * +COMPLETED+ + # * +FAILED+ + # * +CANCELED+ + # * +TERMINATED+ + # * +CONTINUED_AS_NEW+ + # * +TIMED_OUT+ # * +:type_filter+ - (Hash) # * +:name+ - *required* - (String) # * +:version+ - (String) @@ -599,7 +614,10 @@ class Client < Core::JSONClient # Calls the ListDomains API operation. # @param [Hash] options # * +:next_page_token+ - (String) - # * +:registration_status+ - *required* - (String) + # * +:registration_status+ - *required* - (String) Valid values + # include: + # * +REGISTERED+ + # * +DEPRECATED+ # * +:maximum_page_size+ - (Integer) # * +:reverse_order+ - (Boolean) # @return [Core::Response] @@ -654,7 +672,10 @@ class Client < Core::JSONClient # @param [Hash] options # * +:domain+ - *required* - (String) # * +:name+ - (String) - # * +:registration_status+ - *required* - (String) + # * +:registration_status+ - *required* - (String) Valid values + # include: + # * +REGISTERED+ + # * +DEPRECATED+ # * +:next_page_token+ - (String) # * +:maximum_page_size+ - (Integer) # * +:reverse_order+ - (Boolean) @@ -1039,7 +1060,10 @@ class Client < Core::JSONClient # * +:default_execution_start_to_close_timeout+ - (String) # * +:default_task_list+ - (Hash) # * +:name+ - *required* - (String) - # * +:default_child_policy+ - (String) + # * +:default_child_policy+ - (String) Valid values include: + # * +TERMINATE+ + # * +REQUEST_CANCEL+ + # * +ABANDON+ # @return [Core::Response] # @!method request_cancel_workflow_execution(options = {}) @@ -1077,7 +1101,19 @@ class Client < Core::JSONClient # @param [Hash] options # * +:task_token+ - *required* - (String) # * +:decisions+ - (Array) - # * +:decision_type+ - *required* - (String) + # * +:decision_type+ - *required* - (String) Valid values include: + # * +ScheduleActivityTask+ + # * +RequestCancelActivityTask+ + # * +CompleteWorkflowExecution+ + # * +FailWorkflowExecution+ + # * +CancelWorkflowExecution+ + # * +ContinueAsNewWorkflowExecution+ + # * +RecordMarker+ + # * +StartTimer+ + # * +CancelTimer+ + # * +SignalExternalWorkflowExecution+ + # * +RequestCancelExternalWorkflowExecution+ + # * +StartChildWorkflowExecution+ # * +:schedule_activity_task_decision_attributes+ - (Hash) # * +:activity_type+ - *required* - (Hash) # * +:name+ - *required* - (String) @@ -1106,7 +1142,10 @@ class Client < Core::JSONClient # * +:task_list+ - (Hash) # * +:name+ - *required* - (String) # * +:task_start_to_close_timeout+ - (String) - # * +:child_policy+ - (String) + # * +:child_policy+ - (String) Valid values include: + # * +TERMINATE+ + # * +REQUEST_CANCEL+ + # * +ABANDON+ # * +:tag_list+ - (Array) # * +:workflow_type_version+ - (String) # * +:record_marker_decision_attributes+ - (Hash) @@ -1139,7 +1178,10 @@ class Client < Core::JSONClient # * +:task_list+ - (Hash) # * +:name+ - *required* - (String) # * +:task_start_to_close_timeout+ - (String) - # * +:child_policy+ - (String) + # * +:child_policy+ - (String) Valid values include: + # * +TERMINATE+ + # * +REQUEST_CANCEL+ + # * +ABANDON+ # * +:tag_list+ - (Array) # * +:execution_context+ - (String) # @return [Core::Response] @@ -1168,7 +1210,10 @@ class Client < Core::JSONClient # * +:execution_start_to_close_timeout+ - (String) # * +:tag_list+ - (Array) # * +:task_start_to_close_timeout+ - (String) - # * +:child_policy+ - (String) + # * +:child_policy+ - (String) Valid values include: + # * +TERMINATE+ + # * +REQUEST_CANCEL+ + # * +ABANDON+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: @@ -1182,7 +1227,10 @@ class Client < Core::JSONClient # * +:run_id+ - (String) # * +:reason+ - (String) # * +:details+ - (String) - # * +:child_policy+ - (String) + # * +:child_policy+ - (String) Valid values include: + # * +TERMINATE+ + # * +REQUEST_CANCEL+ + # * +ABANDON+ # @return [Core::Response] # end client methods # diff --git a/lib/aws/storage_gateway/client.rb b/lib/aws/storage_gateway/client.rb index 466661a19ad..b25bb900398 100644 --- a/lib/aws/storage_gateway/client.rb +++ b/lib/aws/storage_gateway/client.rb @@ -27,13 +27,46 @@ class Client < Core::JSONClient # @param [Hash] options # * +:activation_key+ - *required* - (String) # * +:gateway_name+ - *required* - (String) - # * +:gateway_timezone+ - *required* - (String) + # * +:gateway_timezone+ - *required* - (String) Valid values include: + # * +GMT-12:00+ + # * +GMT-11:00+ + # * +GMT-10:00+ + # * +GMT-9:00+ + # * +GMT-8:00+ + # * +GMT-7:00+ + # * +GMT-6:00+ + # * +GMT-5:00+ + # * +GMT-4:00+ + # * +GMT-3:30+ + # * +GMT-3:00+ + # * +GMT-2:00+ + # * +GMT-1:00+ + # * +GMT+ + # * +GMT+1:00+ + # * +GMT+2:00+ + # * +GMT+3:00+ + # * +GMT+3:30+ + # * +GMT+4:00+ + # * +GMT+4:30+ + # * +GMT+5:00+ + # * +GMT+5:30+ + # * +GMT+5:45+ + # * +GMT+6:00+ + # * +GMT+7:00+ + # * +GMT+8:00+ + # * +GMT+9:00+ + # * +GMT+9:30+ + # * +GMT+10:00+ + # * +GMT+11:00+ + # * +GMT+12:00+ # * +:gateway_region+ - *required* - (String) - # * +:gateway_type+ - (String) + # * +:gateway_type+ - (String) Valid values include: + # * +STORED+ + # * +CACHED+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) + # * +:gateway_arn+ - (String) # @!method add_cache(options = {}) # Calls the AddCache API operation. @@ -43,7 +76,7 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) + # * +:gateway_arn+ - (String) # @!method add_upload_buffer(options = {}) # Calls the AddUploadBuffer API operation. @@ -53,7 +86,7 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) + # * +:gateway_arn+ - (String) # @!method add_working_storage(options = {}) # Calls the AddWorkingStorage API operation. @@ -63,7 +96,7 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) + # * +:gateway_arn+ - (String) # @!method create_cachedi_scsi_volume(options = {}) # Calls the CreateCachediSCSIVolume API operation. @@ -77,8 +110,8 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +VolumeARN+ - (String) - # * +TargetARN+ - (String) + # * +:volume_arn+ - (String) + # * +:target_arn+ - (String) # @!method create_snapshot(options = {}) # Calls the CreateSnapshot API operation. @@ -88,8 +121,8 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +VolumeARN+ - (String) - # * +SnapshotId+ - (String) + # * +:volume_arn+ - (String) + # * +:snapshot_id+ - (String) # @!method create_snapshot_from_volume_recovery_point(options = {}) # Calls the CreateSnapshotFromVolumeRecoveryPoint API operation. @@ -99,9 +132,9 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +SnapshotId+ - (String) - # * +VolumeARN+ - (String) - # * +VolumeRecoveryPointTime+ - (String) + # * +:snapshot_id+ - (String) + # * +:volume_arn+ - (String) + # * +:volume_recovery_point_time+ - (String) # @!method create_storedi_scsi_volume(options = {}) # Calls the CreateStorediSCSIVolume API operation. @@ -115,19 +148,22 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +VolumeARN+ - (String) - # * +VolumeSizeInBytes+ - (Integer) - # * +TargetARN+ - (String) + # * +:volume_arn+ - (String) + # * +:volume_size_in_bytes+ - (Integer) + # * +:target_arn+ - (String) # @!method delete_bandwidth_rate_limit(options = {}) # Calls the DeleteBandwidthRateLimit API operation. # @param [Hash] options # * +:gateway_arn+ - *required* - (String) - # * +:bandwidth_type+ - *required* - (String) + # * +:bandwidth_type+ - *required* - (String) Valid values include: + # * +UPLOAD+ + # * +DOWNLOAD+ + # * +ALL+ # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) + # * +:gateway_arn+ - (String) # @!method delete_chap_credentials(options = {}) # Calls the DeleteChapCredentials API operation. @@ -137,8 +173,8 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +TargetARN+ - (String) - # * +InitiatorName+ - (String) + # * +:target_arn+ - (String) + # * +:initiator_name+ - (String) # @!method delete_gateway(options = {}) # Calls the DeleteGateway API operation. @@ -147,7 +183,7 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) + # * +:gateway_arn+ - (String) # @!method delete_snapshot_schedule(options = {}) # Calls the DeleteSnapshotSchedule API operation. @@ -156,7 +192,7 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +VolumeARN+ - (String) + # * +:volume_arn+ - (String) # @!method delete_volume(options = {}) # Calls the DeleteVolume API operation. @@ -165,7 +201,7 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +VolumeARN+ - (String) + # * +:volume_arn+ - (String) # @!method describe_bandwidth_rate_limit(options = {}) # Calls the DescribeBandwidthRateLimit API operation. @@ -174,9 +210,9 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) - # * +AverageUploadRateLimitInBitsPerSec+ - (Integer) - # * +AverageDownloadRateLimitInBitsPerSec+ - (Integer) + # * +:gateway_arn+ - (String) + # * +:average_upload_rate_limit_in_bits_per_sec+ - (Integer) + # * +:average_download_rate_limit_in_bits_per_sec+ - (Integer) # @!method describe_cache(options = {}) # Calls the DescribeCache API operation. @@ -185,13 +221,13 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) - # * +DiskIds+ - (Array) - # * +CacheAllocatedInBytes+ - (Integer) - # * +CacheUsedPercentage+ - (Numeric) - # * +CacheDirtyPercentage+ - (Numeric) - # * +CacheHitPercentage+ - (Numeric) - # * +CacheMissPercentage+ - (Numeric) + # * +:gateway_arn+ - (String) + # * +:disk_ids+ - (Array) + # * +:cache_allocated_in_bytes+ - (Integer) + # * +:cache_used_percentage+ - (Numeric) + # * +:cache_dirty_percentage+ - (Numeric) + # * +:cache_hit_percentage+ - (Numeric) + # * +:cache_miss_percentage+ - (Numeric) # @!method describe_cachedi_scsi_volumes(options = {}) # Calls the DescribeCachediSCSIVolumes API operation. @@ -200,20 +236,20 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +CachediSCSIVolumes+ - (Array) - # * +VolumeARN+ - (String) - # * +VolumeId+ - (String) - # * +VolumeType+ - (String) - # * +VolumeStatus+ - (String) - # * +VolumeSizeInBytes+ - (Integer) - # * +VolumeProgress+ - (Numeric) - # * +SourceSnapshotId+ - (String) - # * +VolumeiSCSIAttributes+ - (Hash) - # * +TargetARN+ - (String) - # * +NetworkInterfaceId+ - (String) - # * +NetworkInterfacePort+ - (Integer) - # * +LunNumber+ - (Integer) - # * +ChapEnabled+ - (Boolean) + # * +:cachedi_scsi_volumes+ - (Array) + # * +:volume_arn+ - (String) + # * +:volume_id+ - (String) + # * +:volume_type+ - (String) + # * +:volume_status+ - (String) + # * +:volume_size_in_bytes+ - (Integer) + # * +:volume_progress+ - (Numeric) + # * +:source_snapshot_id+ - (String) + # * +:volumei_scsi_attributes+ - (Hash) + # * +:target_arn+ - (String) + # * +:network_interface_id+ - (String) + # * +:network_interface_port+ - (Integer) + # * +:lun_number+ - (Integer) + # * +:chap_enabled+ - (Boolean) # @!method describe_chap_credentials(options = {}) # Calls the DescribeChapCredentials API operation. @@ -222,11 +258,11 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +ChapCredentials+ - (Array) - # * +TargetARN+ - (String) - # * +SecretToAuthenticateInitiator+ - (String) - # * +InitiatorName+ - (String) - # * +SecretToAuthenticateTarget+ - (String) + # * +:chap_credentials+ - (Array) + # * +:target_arn+ - (String) + # * +:secret_to_authenticate_initiator+ - (String) + # * +:initiator_name+ - (String) + # * +:secret_to_authenticate_target+ - (String) # @!method describe_gateway_information(options = {}) # Calls the DescribeGatewayInformation API operation. @@ -235,16 +271,16 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) - # * +GatewayId+ - (String) - # * +GatewayTimezone+ - (String) - # * +GatewayState+ - (String) - # * +GatewayNetworkInterfaces+ - (Array) - # * +Ipv4Address+ - (String) - # * +MacAddress+ - (String) - # * +Ipv6Address+ - (String) - # * +GatewayType+ - (String) - # * +NextUpdateAvailabilityDate+ - (String) + # * +:gateway_arn+ - (String) + # * +:gateway_id+ - (String) + # * +:gateway_timezone+ - (String) + # * +:gateway_state+ - (String) + # * +:gateway_network_interfaces+ - (Array) + # * +:ipv_4_address+ - (String) + # * +:mac_address+ - (String) + # * +:ipv_6_address+ - (String) + # * +:gateway_type+ - (String) + # * +:next_update_availability_date+ - (String) # @!method describe_maintenance_start_time(options = {}) # Calls the DescribeMaintenanceStartTime API operation. @@ -253,11 +289,11 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) - # * +HourOfDay+ - (Integer) - # * +MinuteOfHour+ - (Integer) - # * +DayOfWeek+ - (Integer) - # * +Timezone+ - (String) + # * +:gateway_arn+ - (String) + # * +:hour_of_day+ - (Integer) + # * +:minute_of_hour+ - (Integer) + # * +:day_of_week+ - (Integer) + # * +:timezone+ - (String) # @!method describe_snapshot_schedule(options = {}) # Calls the DescribeSnapshotSchedule API operation. @@ -266,11 +302,11 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +VolumeARN+ - (String) - # * +StartAt+ - (Integer) - # * +RecurrenceInHours+ - (Integer) - # * +Description+ - (String) - # * +Timezone+ - (String) + # * +:volume_arn+ - (String) + # * +:start_at+ - (Integer) + # * +:recurrence_in_hours+ - (Integer) + # * +:description+ - (String) + # * +:timezone+ - (String) # @!method describe_storedi_scsi_volumes(options = {}) # Calls the DescribeStorediSCSIVolumes API operation. @@ -279,22 +315,22 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +StorediSCSIVolumes+ - (Array) - # * +VolumeARN+ - (String) - # * +VolumeId+ - (String) - # * +VolumeType+ - (String) - # * +VolumeStatus+ - (String) - # * +VolumeSizeInBytes+ - (Integer) - # * +VolumeProgress+ - (Numeric) - # * +VolumeDiskId+ - (String) - # * +SourceSnapshotId+ - (String) - # * +PreservedExistingData+ - (Boolean) - # * +VolumeiSCSIAttributes+ - (Hash) - # * +TargetARN+ - (String) - # * +NetworkInterfaceId+ - (String) - # * +NetworkInterfacePort+ - (Integer) - # * +LunNumber+ - (Integer) - # * +ChapEnabled+ - (Boolean) + # * +:storedi_scsi_volumes+ - (Array) + # * +:volume_arn+ - (String) + # * +:volume_id+ - (String) + # * +:volume_type+ - (String) + # * +:volume_status+ - (String) + # * +:volume_size_in_bytes+ - (Integer) + # * +:volume_progress+ - (Numeric) + # * +:volume_disk_id+ - (String) + # * +:source_snapshot_id+ - (String) + # * +:preserved_existing_data+ - (Boolean) + # * +:volumei_scsi_attributes+ - (Hash) + # * +:target_arn+ - (String) + # * +:network_interface_id+ - (String) + # * +:network_interface_port+ - (Integer) + # * +:lun_number+ - (Integer) + # * +:chap_enabled+ - (Boolean) # @!method describe_upload_buffer(options = {}) # Calls the DescribeUploadBuffer API operation. @@ -303,10 +339,10 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) - # * +DiskIds+ - (Array) - # * +UploadBufferUsedInBytes+ - (Integer) - # * +UploadBufferAllocatedInBytes+ - (Integer) + # * +:gateway_arn+ - (String) + # * +:disk_ids+ - (Array) + # * +:upload_buffer_used_in_bytes+ - (Integer) + # * +:upload_buffer_allocated_in_bytes+ - (Integer) # @!method describe_working_storage(options = {}) # Calls the DescribeWorkingStorage API operation. @@ -315,10 +351,10 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) - # * +DiskIds+ - (Array) - # * +WorkingStorageUsedInBytes+ - (Integer) - # * +WorkingStorageAllocatedInBytes+ - (Integer) + # * +:gateway_arn+ - (String) + # * +:disk_ids+ - (Array) + # * +:working_storage_used_in_bytes+ - (Integer) + # * +:working_storage_allocated_in_bytes+ - (Integer) # @!method list_gateways(options = {}) # Calls the ListGateways API operation. @@ -328,9 +364,9 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +Gateways+ - (Array) - # * +GatewayARN+ - (String) - # * +Marker+ - (String) + # * +:gateways+ - (Array) + # * +:gateway_arn+ - (String) + # * +:marker+ - (String) # @!method list_local_disks(options = {}) # Calls the ListLocalDisks API operation. @@ -339,14 +375,14 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) - # * +Disks+ - (Array) - # * +DiskId+ - (String) - # * +DiskPath+ - (String) - # * +DiskNode+ - (String) - # * +DiskSizeInBytes+ - (Integer) - # * +DiskAllocationType+ - (String) - # * +DiskAllocationResource+ - (String) + # * +:gateway_arn+ - (String) + # * +:disks+ - (Array) + # * +:disk_id+ - (String) + # * +:disk_path+ - (String) + # * +:disk_node+ - (String) + # * +:disk_size_in_bytes+ - (Integer) + # * +:disk_allocation_type+ - (String) + # * +:disk_allocation_resource+ - (String) # @!method list_volume_recovery_points(options = {}) # Calls the ListVolumeRecoveryPoints API operation. @@ -355,12 +391,12 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) - # * +VolumeRecoveryPointInfos+ - (Array) - # * +VolumeARN+ - (String) - # * +VolumeSizeInBytes+ - (Integer) - # * +VolumeUsageInBytes+ - (Integer) - # * +VolumeRecoveryPointTime+ - (String) + # * +:gateway_arn+ - (String) + # * +:volume_recovery_point_infos+ - (Array) + # * +:volume_arn+ - (String) + # * +:volume_size_in_bytes+ - (Integer) + # * +:volume_usage_in_bytes+ - (Integer) + # * +:volume_recovery_point_time+ - (String) # @!method list_volumes(options = {}) # Calls the ListVolumes API operation. @@ -371,11 +407,11 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) - # * +Marker+ - (String) - # * +VolumeInfos+ - (Array) - # * +VolumeARN+ - (String) - # * +VolumeType+ - (String) + # * +:gateway_arn+ - (String) + # * +:marker+ - (String) + # * +:volume_infos+ - (Array) + # * +:volume_arn+ - (String) + # * +:volume_type+ - (String) # @!method shutdown_gateway(options = {}) # Calls the ShutdownGateway API operation. @@ -384,7 +420,7 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) + # * +:gateway_arn+ - (String) # @!method start_gateway(options = {}) # Calls the StartGateway API operation. @@ -393,7 +429,7 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) + # * +:gateway_arn+ - (String) # @!method update_bandwidth_rate_limit(options = {}) # Calls the UpdateBandwidthRateLimit API operation. @@ -404,7 +440,7 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) + # * +:gateway_arn+ - (String) # @!method update_chap_credentials(options = {}) # Calls the UpdateChapCredentials API operation. @@ -416,19 +452,50 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +TargetARN+ - (String) - # * +InitiatorName+ - (String) + # * +:target_arn+ - (String) + # * +:initiator_name+ - (String) # @!method update_gateway_information(options = {}) # Calls the UpdateGatewayInformation API operation. # @param [Hash] options # * +:gateway_arn+ - *required* - (String) # * +:gateway_name+ - (String) - # * +:gateway_timezone+ - (String) - # @return [Core::Response] - # The #data method of the response object returns - # a hash with the following structure: - # * +GatewayARN+ - (String) + # * +:gateway_timezone+ - (String) Valid values include: + # * +GMT-12:00+ + # * +GMT-11:00+ + # * +GMT-10:00+ + # * +GMT-9:00+ + # * +GMT-8:00+ + # * +GMT-7:00+ + # * +GMT-6:00+ + # * +GMT-5:00+ + # * +GMT-4:00+ + # * +GMT-3:30+ + # * +GMT-3:00+ + # * +GMT-2:00+ + # * +GMT-1:00+ + # * +GMT+ + # * +GMT+1:00+ + # * +GMT+2:00+ + # * +GMT+3:00+ + # * +GMT+3:30+ + # * +GMT+4:00+ + # * +GMT+4:30+ + # * +GMT+5:00+ + # * +GMT+5:30+ + # * +GMT+5:45+ + # * +GMT+6:00+ + # * +GMT+7:00+ + # * +GMT+8:00+ + # * +GMT+9:00+ + # * +GMT+9:30+ + # * +GMT+10:00+ + # * +GMT+11:00+ + # * +GMT+12:00+ + # @return [Core::Response] + # The #data method of the response object returns + # a hash with the following structure: + # * +:gateway_arn+ - (String) # @!method update_gateway_software_now(options = {}) # Calls the UpdateGatewaySoftwareNow API operation. @@ -437,7 +504,7 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) + # * +:gateway_arn+ - (String) # @!method update_maintenance_start_time(options = {}) # Calls the UpdateMaintenanceStartTime API operation. @@ -449,7 +516,7 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +GatewayARN+ - (String) + # * +:gateway_arn+ - (String) # @!method update_snapshot_schedule(options = {}) # Calls the UpdateSnapshotSchedule API operation. @@ -461,7 +528,7 @@ class Client < Core::JSONClient # @return [Core::Response] # The #data method of the response object returns # a hash with the following structure: - # * +VolumeARN+ - (String) + # * +:volume_arn+ - (String) # end client methods # From c76594ca6e6cd3c7a9d3a7e954e5ea11c553fbef Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Fri, 15 Mar 2013 10:06:07 -0700 Subject: [PATCH 0470/1398] Added #vpc_id and #vpc methods to AWS::RDS::DbInstance. Fixes #188 --- lib/aws/rds/db_instance.rb | 11 +++++++++++ spec/aws/rds/db_instance_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/lib/aws/rds/db_instance.rb b/lib/aws/rds/db_instance.rb index d2aea60499b..429b5b86ac7 100644 --- a/lib/aws/rds/db_instance.rb +++ b/lib/aws/rds/db_instance.rb @@ -59,6 +59,8 @@ class RDS # # @attr_reader [String] character_set_name # + # @attr_reader [String,nil] vpc_id + # class DBInstance < Core::Resource # @param [String] db_instance_id @@ -75,6 +77,8 @@ def initialize db_instance_id, options = {} alias_method :db_instance_id, :db_instance_identifier + attribute :vpc_id, :from => [:db_subnet_group, :vpc_id], :static => true + attribute :allocated_storage, :static => true, :alias => :size attribute :auto_minor_version_upgrade @@ -136,6 +140,13 @@ def initialize db_instance_id, options = {} resp.data[:db_instances].find{|j| j[:db_instance_identifier] == id } end + # @return [EC2::VPC,nil] + def vpc + if vpc_id + EC2::VPC.new(vpc_id, :config => config) + end + end + # Modifies the database instance. # @note You do not need to set +:db_instance_identifier+. # @see Client#modify_db_instance diff --git a/spec/aws/rds/db_instance_spec.rb b/spec/aws/rds/db_instance_spec.rb index 217998c4a0d..ceaf339c66c 100644 --- a/spec/aws/rds/db_instance_spec.rb +++ b/spec/aws/rds/db_instance_spec.rb @@ -78,6 +78,9 @@ class RDS :preferred_maintenance_window => 'window2', :read_replica_db_instance_identifiers => %w(abc xyz), :read_replica_source_db_instance_identifier => 'mno', + :db_subnet_group => { + :vpc_id => 'vpc-123', + }, }} it 'extracts attributes' do @@ -104,6 +107,30 @@ class RDS i.preferred_maintenance_window.should eq('window2') i.read_replica_db_instance_identifiers.should eq(['abc','xyz']) i.read_replica_source_db_instance_identifier.should eq('mno') + i.vpc_id.should eq('vpc-123') + end + + end + + context '#vpc' do + + let(:details) {{ + :db_instance_identifier => instance.id, + :db_subnet_group => { + :vpc_id => 'vpc-123', + }, + }} + + it 'returns an EC2::VPC object' do + vpc = instance.vpc + vpc.should be_a(EC2::VPC) + vpc.id.should eq('vpc-123') + vpc.config.should eq(instance.config) + end + + it 'returns nil when it does not have a VPC id' do + instance.stub(:vpc_id).and_return(nil) + instance.vpc.should be(nil) end end From 6fab6fec6a4dfeae9cb67962abe98b44631e5930 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Mon, 18 Mar 2013 09:36:26 -0700 Subject: [PATCH 0471/1398] Now using us-west-2 as the default sample region for documentation. --- lib/aws/auto_scaling.rb | 2 +- lib/aws/auto_scaling/group.rb | 2 +- lib/aws/auto_scaling/group_collection.rb | 2 +- lib/aws/core/log_formatter.rb | 2 +- lib/aws/ec2/attachment.rb | 2 +- lib/aws/ec2/availability_zone.rb | 2 +- lib/aws/ec2/region.rb | 2 +- lib/aws/ec2/region_collection.rb | 2 +- lib/aws/ec2/volume.rb | 2 +- lib/aws/ec2/volume_collection.rb | 2 +- lib/aws/elb/availability_zone_collection.rb | 8 ++++---- lib/aws/elb/load_balancer.rb | 4 ++-- lib/aws/elb/load_balancer_collection.rb | 2 +- lib/aws/sns/topic.rb | 2 +- lib/aws/sns/topic_collection.rb | 2 +- lib/aws/sqs/queue_collection.rb | 2 +- 16 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/aws/auto_scaling.rb b/lib/aws/auto_scaling.rb index 8c6d6ff2ad2..a16316724ef 100644 --- a/lib/aws/auto_scaling.rb +++ b/lib/aws/auto_scaling.rb @@ -61,7 +61,7 @@ module AWS # # group = auto_scaling.groups.create('group-name', # :launch_configuration => launch_config, - # :availability_zones => %w(us-east-1a us-east-1b), + # :availability_zones => %w(us-west-2a us-west-2b), # :min_size => 1, # :max_size => 4) # diff --git a/lib/aws/auto_scaling/group.rb b/lib/aws/auto_scaling/group.rb index 667756db7cf..db321d976df 100644 --- a/lib/aws/auto_scaling/group.rb +++ b/lib/aws/auto_scaling/group.rb @@ -163,7 +163,7 @@ def auto_scaling_instances # Auto Scaling group. You can use this collection to further refine # the instances you are interested in: # - # group.ec2_instances.filter('availability-zone', 'us-east-1a').each do |i| + # group.ec2_instances.filter('availability-zone', 'us-west-2a').each do |i| # puts instance.id # end # diff --git a/lib/aws/auto_scaling/group_collection.rb b/lib/aws/auto_scaling/group_collection.rb index eaec467b908..4bb82bc6bab 100644 --- a/lib/aws/auto_scaling/group_collection.rb +++ b/lib/aws/auto_scaling/group_collection.rb @@ -23,7 +23,7 @@ class GroupCollection # # group = auto_scaling.groups.create('group-name', # :launch_configuration => 'launch-config-name', - # :availability_zones => %(us-east-1a us-east-1b), + # :availability_zones => %(us-west-2a us-west-2b), # :min_size => 1, # :max_size => 4) # diff --git a/lib/aws/core/log_formatter.rb b/lib/aws/core/log_formatter.rb index f83b0c6b488..5670ebe84e3 100644 --- a/lib/aws/core/log_formatter.rb +++ b/lib/aws/core/log_formatter.rb @@ -58,7 +58,7 @@ module Core # The AWS service name (e.g. 'S3', 'EC2', 'SimpleDB', etc) # # [+:region+] - # The AWS region name (e.g. 'us-east-1', 'us-west-1', etc) + # The AWS region name (e.g. 'us-west-1', 'us-west-2', etc) # # [+:operation+] # The name of the client request method. This maps to the name of diff --git a/lib/aws/ec2/attachment.rb b/lib/aws/ec2/attachment.rb index 60246fdff65..99778071d47 100644 --- a/lib/aws/ec2/attachment.rb +++ b/lib/aws/ec2/attachment.rb @@ -18,7 +18,7 @@ class EC2 # # @example Create an empty 15GiB volume and attach it to an instance # volume = ec2.volumes.create(:size => 15, - # :availability_zone => "us-east-1a") + # :availability_zone => "us-west-2a") # attachment = volume.attach_to(ec2.instances["i-123"], "/dev/sdf") # sleep 1 until attachment.status != :attaching # diff --git a/lib/aws/ec2/availability_zone.rb b/lib/aws/ec2/availability_zone.rb index 0d0d9ea81cb..3f7932311be 100644 --- a/lib/aws/ec2/availability_zone.rb +++ b/lib/aws/ec2/availability_zone.rb @@ -39,7 +39,7 @@ def initialize name, options = {} end # @return [String] Returns the name of the availability zone, - # e.g. "us-east-1a". + # e.g. "us-west-2a". attr_reader :name alias_method :to_s, :name diff --git a/lib/aws/ec2/region.rb b/lib/aws/ec2/region.rb index 526fff6f941..8286682b300 100644 --- a/lib/aws/ec2/region.rb +++ b/lib/aws/ec2/region.rb @@ -40,7 +40,7 @@ def initialize name, options = {} @config = @client.config end - # @return [String] The name of the region (e.g. "us-east-1"). + # @return [String] The name of the region (e.g. "us-west-2"). attr_reader :name # @return [String] diff --git a/lib/aws/ec2/region_collection.rb b/lib/aws/ec2/region_collection.rb index b44caa6e57f..f03d090a29f 100644 --- a/lib/aws/ec2/region_collection.rb +++ b/lib/aws/ec2/region_collection.rb @@ -34,7 +34,7 @@ def each end # @return [Region] The region identified by the given name - # (e.g. "us-east-1"). + # (e.g. "us-west-2"). def [](name) super end diff --git a/lib/aws/ec2/volume.rb b/lib/aws/ec2/volume.rb index 819b58a9872..db95320e900 100644 --- a/lib/aws/ec2/volume.rb +++ b/lib/aws/ec2/volume.rb @@ -18,7 +18,7 @@ class EC2 # # @example Create an empty 15GiB volume and attach it to an instance # volume = ec2.volumes.create(:size => 15, - # :availability_zone => "us-east-1a") + # :availability_zone => "us-west-2a") # attachment = volume.attach_to(ec2.instances["i-123"], "/dev/sdf") # sleep 1 until attachment.status != :attaching # diff --git a/lib/aws/ec2/volume_collection.rb b/lib/aws/ec2/volume_collection.rb index 34fe51ca5fb..9904ff8ef53 100644 --- a/lib/aws/ec2/volume_collection.rb +++ b/lib/aws/ec2/volume_collection.rb @@ -19,7 +19,7 @@ class EC2 # # @example Create an empty 15GiB volume # ec2.volumes.create(:size => 15, - # :availability_zone => "us-east-1a") + # :availability_zone => "us-west-2a") # # @example Get a volume by ID # volume = ec2.volumes["vol-123"] diff --git a/lib/aws/elb/availability_zone_collection.rb b/lib/aws/elb/availability_zone_collection.rb index 893c5703236..2f1710f97b9 100644 --- a/lib/aws/elb/availability_zone_collection.rb +++ b/lib/aws/elb/availability_zone_collection.rb @@ -22,10 +22,10 @@ class ELB # zones = load_balancer.availability_zones # # # adding zones - # zones.enable('us-east-1b', 'us-east-1c') + # zones.enable('us-west-2b', 'us-west-2c') # # # removing zones - # zones.disable('us-east-1b') + # zones.disable('us-west-2b') # # # enumerating enabled zones # zones.each do |zone| @@ -48,7 +48,7 @@ def initialize load_balancer, options = {} # Adds one or more EC2 Availability Zones to the load balancer. # - # load_balancer.availability_zones.enable("us-east-1a", "us-east-1b") + # load_balancer.availability_zones.enable("us-west-2a", "us-west-2b") # # You can also pass {EC2::AvailabilityZone} objects: # @@ -83,7 +83,7 @@ def enable *availability_zones # Removes the specified EC2 availability zones from the set of # configured availability zones for the load balancer. # - # load_balancer.availability_zones.disable("us-east-1a", "us-east-1b") + # load_balancer.availability_zones.disable("us-west-2a", "us-west-2b") # # You can also pass {EC2::AvailabilityZone} objects: # diff --git a/lib/aws/elb/load_balancer.rb b/lib/aws/elb/load_balancer.rb index cc45948c276..f28fe1bdab8 100644 --- a/lib/aws/elb/load_balancer.rb +++ b/lib/aws/elb/load_balancer.rb @@ -115,11 +115,11 @@ def initialize name, options = {} # # @example enable an availability zone # - # load_balancer.availability_zones.enable('us-east-1b') + # load_balancer.availability_zones.enable('us-west-2b') # # @example disable an availability zone # - # load_balancer.availability_zones.disable('us-east-1b') + # load_balancer.availability_zones.disable('us-west-2b') # # @example list enabled availability zoens # diff --git a/lib/aws/elb/load_balancer_collection.rb b/lib/aws/elb/load_balancer_collection.rb index d19148219f9..be6b800eb04 100644 --- a/lib/aws/elb/load_balancer_collection.rb +++ b/lib/aws/elb/load_balancer_collection.rb @@ -29,7 +29,7 @@ class LoadBalancerCollection # with a single listener: # # load_balancer = elb.load_balancers.create('my-load-balancer', - # :availability_zones => %w(us-east-1a us-east-1b), + # :availability_zones => %w(us-west-2a us-west-2b), # :listeners => [{ # :port => 80, # :protocol => :http, diff --git a/lib/aws/sns/topic.rb b/lib/aws/sns/topic.rb index ca8a183f5db..c0b00a52a79 100644 --- a/lib/aws/sns/topic.rb +++ b/lib/aws/sns/topic.rb @@ -81,7 +81,7 @@ def name # # # you must manage the queue policy yourself to allow the # # the topic to send messages (policy action 'sqs:SendMessage') - # topic.subscribe('arn:aws:sqs:us-east-1:123456789123:AQueue') + # topic.subscribe('arn:aws:sqs:us-west-2:123456789123:AQueue') # # @example SQS Queue (by Queue object) # diff --git a/lib/aws/sns/topic_collection.rb b/lib/aws/sns/topic_collection.rb index 9d8a107bc48..d16b5bde019 100644 --- a/lib/aws/sns/topic_collection.rb +++ b/lib/aws/sns/topic_collection.rb @@ -28,7 +28,7 @@ def create name # @param [String] topic_arn An AWS SNS Topic ARN. It should be # formatted something like: # - # arn:aws:sns:us-east-1:123456789012:TopicName + # arn:aws:sns:us-west-2:123456789012:TopicName # # @return [Topic] Returns a topic with the given Topic ARN. def [] topic_arn diff --git a/lib/aws/sqs/queue_collection.rb b/lib/aws/sqs/queue_collection.rb index a7e93974d33..4da4d163765 100644 --- a/lib/aws/sqs/queue_collection.rb +++ b/lib/aws/sqs/queue_collection.rb @@ -27,7 +27,7 @@ class SQS # pp sqs.queues.with_prefix("production_").map(&:url) # # @example Accessing a queue by URL - # url = "http://sqs.us-east-1.amazonaws.com/123456789012/myqueue" + # url = "http://sqs.us-west-2.amazonaws.com/123456789012/myqueue" # sqs.queues[url].send_message("HELLO") class QueueCollection From 070b0c6bb562cc5d3ae26cdd52c17328e038f4bd Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Tue, 19 Mar 2013 09:12:22 -0700 Subject: [PATCH 0472/1398] Corrected typo in SQS::Queue documentation. --- lib/aws/sqs/queue.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/aws/sqs/queue.rb b/lib/aws/sqs/queue.rb index 40573eb84ef..4920d056430 100644 --- a/lib/aws/sqs/queue.rb +++ b/lib/aws/sqs/queue.rb @@ -160,7 +160,7 @@ def send_message body, options = {} # See {#wait_time_seconds} to set the global long poll setting # on the queue. # - # @option opts [Integer] :visibilitiy_timeout The duration (in + # @option opts [Integer] :visibility_timeout The duration (in # seconds) that the received messages are hidden from # subsequent retrieve requests. Valid values: integer from # 0 to 43200 (maximum 12 hours) @@ -246,7 +246,7 @@ def receive_message(opts = {}, &block) # messages are received one at a time. Valid values: # integers from 1 to 10. # - # @option opts [Integer] :visibilitiy_timeout The duration (in + # @option opts [Integer] :visibility_timeout The duration (in # seconds) that the received messages are hidden from # subsequent retrieve requests. Valid values: integer from # 0 to 43200 (maximum 12 hours) From b38d7e57b4e543e4ec9ff060e29ffe83efd31756 Mon Sep 17 00:00:00 2001 From: Patrick Cheng Date: Tue, 19 Mar 2013 22:55:06 -0700 Subject: [PATCH 0473/1398] parameters was not being passed --- lib/aws/cloud_formation.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/aws/cloud_formation.rb b/lib/aws/cloud_formation.rb index 345fee331c9..a020e1cee48 100644 --- a/lib/aws/cloud_formation.rb +++ b/lib/aws/cloud_formation.rb @@ -263,6 +263,7 @@ def validate_template template def estimate_template_cost template, parameters = {} client_opts = {} client_opts[:template] = template + client_opts[:parameters] = parameters apply_template(client_opts) apply_parameters(client_opts) client.estimate_template_cost(client_opts).url From c7d66c30e3c49b7e81169c07aea7183f7da87828 Mon Sep 17 00:00:00 2001 From: Loren Segal Date: Wed, 20 Mar 2013 14:16:48 -0700 Subject: [PATCH 0474/1398] Update CloudFormation specs to reflect new API --- spec/aws/cloud_formation_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/aws/cloud_formation_spec.rb b/spec/aws/cloud_formation_spec.rb index 810223c652e..c04ea152fe1 100644 --- a/spec/aws/cloud_formation_spec.rb +++ b/spec/aws/cloud_formation_spec.rb @@ -147,14 +147,14 @@ module AWS it 'calls #estimate_template_cost on the client' do client.should_receive(:estimate_template_cost). - with(:template_body => 'template'). + with(:parameters => [], :template_body => 'template'). and_return(resp) cloud_formation.estimate_template_cost('template') end it 'accepts urls' do client.should_receive(:estimate_template_cost). - with(:template_url => 'http://domain.com/template'). + with(:parameters => [], :template_url => 'http://domain.com/template'). and_return(resp) cloud_formation.estimate_template_cost('http://domain.com/template') end From 018619a80ec768d7d7e1fea22c6155b5442209fc Mon Sep 17 00:00:00 2001 From: Loren Segal Date: Wed, 20 Mar 2013 14:23:05 -0700 Subject: [PATCH 0475/1398] Only load config file if it exists --- features/support/common.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/support/common.rb b/features/support/common.rb index 6421dc4e9ac..51bd7515e06 100644 --- a/features/support/common.rb +++ b/features/support/common.rb @@ -61,7 +61,7 @@ end AfterConfiguration do - AWS.config(test_config) + AWS.config(test_config) if test_config handler = AWS::Core::Http::Handler.new(AWS.config.http_handler) do |req, resp, read_block| (@requests_made ||= []) << req super(req, resp, &read_block) From 2f47346c64087c1b66a37dd762b9b89162fa58a6 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Thu, 21 Mar 2013 09:38:06 -0700 Subject: [PATCH 0476/1398] Resolved a thready-safety issue with AWS::Core::Configuration. References #90 --- lib/aws/core/configuration.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/aws/core/configuration.rb b/lib/aws/core/configuration.rb index 702d4437c46..04bb740d6f9 100644 --- a/lib/aws/core/configuration.rb +++ b/lib/aws/core/configuration.rb @@ -413,9 +413,10 @@ def add_option_with_needs name, needs, &create_block needed = needs.inject({}) {|h,need| h.merge(need => send(need)) } unless @created.key?(name) and @created[name][:needed] == needed - @created[name] = {} - @created[name][:object] = create_block.call(self,needed) - @created[name][:needed] = needed + created = {} + created[:object] = create_block.call(self,needed) + created[:needed] = needed + @created[name] = created end @created[name][:object] From c971784f7bbc5ec7938b3dd43c53d76a50e0b3dc Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Thu, 21 Mar 2013 15:15:33 -0700 Subject: [PATCH 0477/1398] No longer catching all 400-level errors when calling Bucket#exists? Fixes #205 --- lib/aws/s3/bucket.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aws/s3/bucket.rb b/lib/aws/s3/bucket.rb index ed3c75ced89..7bee4b04fa6 100644 --- a/lib/aws/s3/bucket.rb +++ b/lib/aws/s3/bucket.rb @@ -510,7 +510,7 @@ def exists? true rescue Errors::NoSuchBucket => e false # bucket does not exist - rescue Errors::ClientError => e + rescue Errors::AccessDenied => e true # bucket exists end end From 5e456085c0dd21de499b48daaa0deb5ea7c34184 Mon Sep 17 00:00:00 2001 From: ebargtuo Date: Sat, 23 Mar 2013 00:49:35 +0100 Subject: [PATCH 0478/1398] Added additional header (expires) returned by AWS::S3::Client#head_object and #get_object --- lib/aws/s3/client.rb | 1 + spec/aws/s3/client_spec.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/lib/aws/s3/client.rb b/lib/aws/s3/client.rb index c3f9eff8191..1ecdf5c016a 100644 --- a/lib/aws/s3/client.rb +++ b/lib/aws/s3/client.rb @@ -1466,6 +1466,7 @@ def extract_object_headers resp 'content-type' => :content_type, 'content-encoding' => :content_encoding, 'cache-control' => :cache_control, + 'expires' => :expires, 'etag' => :etag, 'x-amz-website-redirect-location' => :website_redirect_location, 'accept-ranges' => :accept_ranges, diff --git a/spec/aws/s3/client_spec.rb b/spec/aws/s3/client_spec.rb index 09789edca2d..1cd4482fd51 100644 --- a/spec/aws/s3/client_spec.rb +++ b/spec/aws/s3/client_spec.rb @@ -1324,6 +1324,7 @@ def should_determine_content_length_for data, length resp.headers['content-type'] = ['text/plain'] resp.headers['content-encoding'] = ['gzip'] resp.headers['cache-control'] = ['max-age=1296000'] + resp.headers['expires'] = ['Sat, 22 Mar 2014 14:14:21 GMT'] resp.headers['accept-ranges'] = ['bytes'] resp.headers['x-amz-meta-Color'] = ['red'] resp.headers['x-amz-meta-foo'] = 'bar' @@ -1512,6 +1513,7 @@ def should_determine_content_length_for data, length resp.headers['content-type'] = ['text/plain'] resp.headers['content-encoding'] = ['gzip'] resp.headers['cache-control'] = ['max-age=1296000'] + resp.headers['expires'] = ['Sat, 22 Mar 2014 14:14:21 GMT'] resp.headers['accept-ranges'] = ['bytes'] resp.headers['x-amz-meta-Color'] = ['red'] resp.headers['x-amz-meta-foo'] = 'bar' @@ -1519,6 +1521,7 @@ def should_determine_content_length_for data, length r[:content_type].should eq('text/plain') r[:content_encoding].should eq('gzip') r[:cache_control].should eq('max-age=1296000') + r[:expires].should eq('Sat, 22 Mar 2014 14:14:21 GMT') r[:accept_ranges].should eq('bytes') r[:meta].should eq('Color' => 'red', 'foo' => 'bar') end From 3640ea8ab4b700efef05fac3ef50d28cd4342a88 Mon Sep 17 00:00:00 2001 From: tsukasaoishi Date: Mon, 25 Mar 2013 23:02:50 +0900 Subject: [PATCH 0479/1398] add to_s method, because Rails.version of edge rails is Gem::Version object --- lib/aws/rails.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aws/rails.rb b/lib/aws/rails.rb index a452c095be3..fb7bd023e01 100644 --- a/lib/aws/rails.rb +++ b/lib/aws/rails.rb @@ -151,7 +151,7 @@ def self.load_yaml_config # def self.add_action_mailer_delivery_method name = :amazon_ses, options = {} - if ::Rails.version.to_f >= 3 + if ::Rails.version.to_s.to_f >= 3 ActiveSupport.on_load(:action_mailer) do self.add_delivery_method(name, AWS::SimpleEmailService, options) end From 4467c72a1ddddad8093309aaef05c54b8fccccf6 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Fri, 22 Mar 2013 08:33:16 -0700 Subject: [PATCH 0480/1398] Replaced the AWS.eager_autoload! implementation. This implementation no longer relies on the AWS.register_autoloads hook, runs faster and is simpler. --- lib/aws/core.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/aws/core.rb b/lib/aws/core.rb index f43c0dbcce7..a57beef254b 100644 --- a/lib/aws/core.rb +++ b/lib/aws/core.rb @@ -583,5 +583,19 @@ def stub! nil end + # Eagerly loads all AWS classes/modules registered with autoload. + # @return [nil] + def eager_autoload! klass_or_module = AWS + klass_or_module.constants.each do |const_name| + if path = klass_or_module.autoload?(const_name) + require(path) + if const = klass_or_module.const_get(const_name) and const.is_a?(Module) + eager_autoload!(const) + end + end + end + nil + end + end end From 66e7a1d607f03716520a59d0bfed88d0718d46cd Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Fri, 22 Mar 2013 08:34:58 -0700 Subject: [PATCH 0481/1398] Removed the usage of AWS.register_autoloads, now using vanilla autoload. --- lib/aws-sdk.rb | 60 +++++++------ lib/aws/auto_scaling.rb | 44 +++++----- lib/aws/cloud_formation.rb | 28 +++--- lib/aws/cloud_front.rb | 8 +- lib/aws/cloud_search.rb | 8 +- lib/aws/cloud_watch.rb | 24 +++--- lib/aws/core.rb | 145 ++++++++++++++------------------ lib/aws/core/collection.rb | 8 +- lib/aws/data_pipeline.rb | 8 +- lib/aws/dynamo_db.rb | 36 ++++---- lib/aws/ec2.rb | 127 ++++++++++++++-------------- lib/aws/ec2/security_group.rb | 10 +-- lib/aws/elastic_beanstalk.rb | 8 +- lib/aws/elastic_transcoder.rb | 8 +- lib/aws/elasticache.rb | 8 +- lib/aws/elb.rb | 28 +++--- lib/aws/emr.rb | 16 ++-- lib/aws/glacier.rb | 20 ++--- lib/aws/iam.rb | 58 ++++++------- lib/aws/iam/collection.rb | 2 +- lib/aws/import_export.rb | 8 +- lib/aws/ops_works.rb | 8 +- lib/aws/rds.rb | 16 ++-- lib/aws/record.rb | 10 +-- lib/aws/redshift.rb | 8 +- lib/aws/route_53.rb | 26 +++--- lib/aws/s3.rb | 68 ++++++++------- lib/aws/s3/client.rb | 4 +- lib/aws/s3/tree.rb | 12 ++- lib/aws/simple_db.rb | 32 ++++--- lib/aws/simple_email_service.rb | 16 ++-- lib/aws/simple_workflow.rb | 46 +++++----- lib/aws/sns.rb | 22 +++-- lib/aws/sqs.rb | 18 ++-- lib/aws/storage_gateway.rb | 8 +- lib/aws/sts.rb | 14 ++- 36 files changed, 443 insertions(+), 527 deletions(-) diff --git a/lib/aws-sdk.rb b/lib/aws-sdk.rb index e30fba82e66..f4ef807ea2e 100644 --- a/lib/aws-sdk.rb +++ b/lib/aws-sdk.rb @@ -43,37 +43,35 @@ require 'aws/sts/config' module AWS - register_autoloads(self) do - autoload :AutoScaling, 'auto_scaling' - autoload :CloudFormation, 'cloud_formation' - autoload :CloudFront, 'cloud_front' - autoload :CloudSearch, 'cloud_search' - autoload :CloudWatch, 'cloud_watch' - autoload :DataPipeline, 'data_pipeline' - autoload :DynamoDB, 'dynamo_db' - autoload :EC2, 'ec2' - autoload :ElastiCache, 'elasticache' - autoload :ElasticBeanstalk, 'elastic_beanstalk' - autoload :ElasticTranscoder, 'elastic_transcoder' - autoload :EMR, 'emr' - autoload :ELB, 'elb' - autoload :IAM, 'iam' - autoload :ImportExport, 'import_export' - autoload :Glacier, 'glacier' - autoload :OpsWorks, 'ops_works' - autoload :Redshift, 'redshift' - autoload :RDS, 'rds' - autoload :Route53, 'route_53' - autoload :S3, 's3' - autoload :SimpleDB, 'simple_db' - autoload :SimpleEmailService, 'simple_email_service' - autoload :SimpleWorkflow, 'simple_workflow' - autoload :SNS, 'sns' - autoload :SQS, 'sqs' - autoload :StorageGateway, 'storage_gateway' - autoload :STS, 'sts' - autoload :Record, 'record' - end + autoload :AutoScaling, 'aws/auto_scaling' + autoload :CloudFormation, 'aws/cloud_formation' + autoload :CloudFront, 'aws/cloud_front' + autoload :CloudSearch, 'aws/cloud_search' + autoload :CloudWatch, 'aws/cloud_watch' + autoload :DataPipeline, 'aws/data_pipeline' + autoload :DynamoDB, 'aws/dynamo_db' + autoload :EC2, 'aws/ec2' + autoload :ElastiCache, 'aws/elasticache' + autoload :ElasticBeanstalk, 'aws/elastic_beanstalk' + autoload :ElasticTranscoder, 'aws/elastic_transcoder' + autoload :EMR, 'aws/emr' + autoload :ELB, 'aws/elb' + autoload :IAM, 'aws/iam' + autoload :ImportExport, 'aws/import_export' + autoload :Glacier, 'aws/glacier' + autoload :OpsWorks, 'aws/ops_works' + autoload :Redshift, 'aws/redshift' + autoload :RDS, 'aws/rds' + autoload :Route53, 'aws/route_53' + autoload :S3, 'aws/s3' + autoload :SimpleDB, 'aws/simple_db' + autoload :SimpleEmailService, 'aws/simple_email_service' + autoload :SimpleWorkflow, 'aws/simple_workflow' + autoload :SNS, 'aws/sns' + autoload :SQS, 'aws/sqs' + autoload :StorageGateway, 'aws/storage_gateway' + autoload :STS, 'aws/sts' + autoload :Record, 'aws/record' end require 'aws/rails' diff --git a/lib/aws/auto_scaling.rb b/lib/aws/auto_scaling.rb index a16316724ef..09a76ba7553 100644 --- a/lib/aws/auto_scaling.rb +++ b/lib/aws/auto_scaling.rb @@ -69,29 +69,27 @@ module AWS # @return [Client] the low-level AutoScaling client object class AutoScaling - AWS.register_autoloads(self, 'aws/auto_scaling') do - autoload :Activity, 'activity' - autoload :ActivityCollection, 'activity_collection' - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Group, 'group' - autoload :GroupCollection, 'group_collection' - autoload :GroupOptions, 'group_options' - autoload :Instance, 'instance' - autoload :InstanceCollection, 'instance_collection' - autoload :LaunchConfiguration, 'launch_configuration' - autoload :LaunchConfigurationCollection, 'launch_configuration_collection' - autoload :NotificationConfiguration, 'notification_configuration' - autoload :NotificationConfigurationCollection, 'notification_configuration_collection' - autoload :Request, 'request' - autoload :ScalingPolicy, 'scaling_policy' - autoload :ScalingPolicyCollection, 'scaling_policy_collection' - autoload :ScalingPolicyOptions, 'scaling_policy_options' - autoload :ScheduledAction, 'scheduled_action' - autoload :ScheduledActionCollection, 'scheduled_action_collection' - autoload :Tag, 'tag' - autoload :TagCollection, 'tag_collection' - end + autoload :Activity, 'aws/auto_scaling/activity' + autoload :ActivityCollection, 'aws/auto_scaling/activity_collection' + autoload :Client, 'aws/auto_scaling/client' + autoload :Errors, 'aws/auto_scaling/errors' + autoload :Group, 'aws/auto_scaling/group' + autoload :GroupCollection, 'aws/auto_scaling/group_collection' + autoload :GroupOptions, 'aws/auto_scaling/group_options' + autoload :Instance, 'aws/auto_scaling/instance' + autoload :InstanceCollection, 'aws/auto_scaling/instance_collection' + autoload :LaunchConfiguration, 'aws/auto_scaling/launch_configuration' + autoload :LaunchConfigurationCollection, 'aws/auto_scaling/launch_configuration_collection' + autoload :NotificationConfiguration, 'aws/auto_scaling/notification_configuration' + autoload :NotificationConfigurationCollection, 'aws/auto_scaling/notification_configuration_collection' + autoload :Request, 'aws/auto_scaling/request' + autoload :ScalingPolicy, 'aws/auto_scaling/scaling_policy' + autoload :ScalingPolicyCollection, 'aws/auto_scaling/scaling_policy_collection' + autoload :ScalingPolicyOptions, 'aws/auto_scaling/scaling_policy_options' + autoload :ScheduledAction, 'aws/auto_scaling/scheduled_action' + autoload :ScheduledActionCollection, 'aws/auto_scaling/scheduled_action_collection' + autoload :Tag, 'aws/auto_scaling/tag' + autoload :TagCollection, 'aws/auto_scaling/tag_collection' include Core::ServiceInterface diff --git a/lib/aws/cloud_formation.rb b/lib/aws/cloud_formation.rb index a020e1cee48..baac1304c25 100644 --- a/lib/aws/cloud_formation.rb +++ b/lib/aws/cloud_formation.rb @@ -145,21 +145,19 @@ module AWS # @return [Client] the low-level CloudFormation client object class CloudFormation - AWS.register_autoloads(self, 'aws/cloud_formation') do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Request, 'request' - autoload :Stack, 'stack' - autoload :StackCollection, 'stack_collection' - autoload :StackEvent, 'stack_event' - autoload :StackEventCollection, 'stack_event_collection' - autoload :StackOptions, 'stack_options' - autoload :StackOutput, 'stack_output' - autoload :StackSummaryCollection, 'stack_summary_collection' - autoload :StackResource, 'stack_resource' - autoload :StackResourceCollection, 'stack_resource_collection' - autoload :StackResourceSummaryCollection, 'stack_resource_summary_collection' - end + autoload :Client, 'aws/cloud_formation/client' + autoload :Errors, 'aws/cloud_formation/errors' + autoload :Request, 'aws/cloud_formation/request' + autoload :Stack, 'aws/cloud_formation/stack' + autoload :StackCollection, 'aws/cloud_formation/stack_collection' + autoload :StackEvent, 'aws/cloud_formation/stack_event' + autoload :StackEventCollection, 'aws/cloud_formation/stack_event_collection' + autoload :StackOptions, 'aws/cloud_formation/stack_options' + autoload :StackOutput, 'aws/cloud_formation/stack_output' + autoload :StackSummaryCollection, 'aws/cloud_formation/stack_summary_collection' + autoload :StackResource, 'aws/cloud_formation/stack_resource' + autoload :StackResourceCollection, 'aws/cloud_formation/stack_resource_collection' + autoload :StackResourceSummaryCollection, 'aws/cloud_formation/stack_resource_summary_collection' include Core::ServiceInterface include StackOptions diff --git a/lib/aws/cloud_front.rb b/lib/aws/cloud_front.rb index 6f22d85016b..a948a339ecd 100644 --- a/lib/aws/cloud_front.rb +++ b/lib/aws/cloud_front.rb @@ -61,11 +61,9 @@ module AWS # @return [Client] the low-level CloudFront client object class CloudFront - AWS.register_autoloads(self, 'aws/cloud_front') do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Request, 'request' - end + autoload :Client, 'aws/cloud_front/client' + autoload :Errors, 'aws/cloud_front/errors' + autoload :Request, 'aws/cloud_front/request' include Core::ServiceInterface diff --git a/lib/aws/cloud_search.rb b/lib/aws/cloud_search.rb index 9cea899c76f..b2973b4cd7c 100644 --- a/lib/aws/cloud_search.rb +++ b/lib/aws/cloud_search.rb @@ -61,11 +61,9 @@ module AWS # @return [Client] the low-level CloudSearch client object class CloudSearch - AWS.register_autoloads(self, 'aws/cloud_search') do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Request, 'request' - end + autoload :Client, 'aws/cloud_search/client' + autoload :Errors, 'aws/cloud_search/errors' + autoload :Request, 'aws/cloud_search/request' include Core::ServiceInterface diff --git a/lib/aws/cloud_watch.rb b/lib/aws/cloud_watch.rb index d4990aa64e1..f49058bdd8a 100644 --- a/lib/aws/cloud_watch.rb +++ b/lib/aws/cloud_watch.rb @@ -61,19 +61,17 @@ module AWS # @return [Client] the low-level CloudWatch client object class CloudWatch - AWS.register_autoloads(self, 'aws/cloud_watch') do - autoload :Alarm, 'alarm' - autoload :AlarmCollection, 'alarm_collection' - autoload :AlarmHistoryItem, 'alarm_history_item' - autoload :AlarmHistoryItemCollection, 'alarm_history_item_collection' - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Metric, 'metric' - autoload :MetricCollection, 'metric_collection' - autoload :MetricAlarmCollection, 'metric_alarm_collection' - autoload :MetricStatistics, 'metric_statistics' - autoload :Request, 'request' - end + autoload :Alarm, 'aws/cloud_watch/alarm' + autoload :AlarmCollection, 'aws/cloud_watch/alarm_collection' + autoload :AlarmHistoryItem, 'aws/cloud_watch/alarm_history_item' + autoload :AlarmHistoryItemCollection, 'aws/cloud_watch/alarm_history_item_collection' + autoload :Client, 'aws/cloud_watch/client' + autoload :Errors, 'aws/cloud_watch/errors' + autoload :Metric, 'aws/cloud_watch/metric' + autoload :MetricCollection, 'aws/cloud_watch/metric_collection' + autoload :MetricAlarmCollection, 'aws/cloud_watch/metric_alarm_collection' + autoload :MetricStatistics, 'aws/cloud_watch/metric_statistics' + autoload :Request, 'aws/cloud_watch/request' include Core::ServiceInterface diff --git a/lib/aws/core.rb b/lib/aws/core.rb index a57beef254b..669c6ced953 100644 --- a/lib/aws/core.rb +++ b/lib/aws/core.rb @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. require 'aws/version' -require 'aws/core/autoloader' # AWS is the root module for all of the Amazon Web Services. It is also # where you can configure you access to AWS. @@ -77,103 +76,89 @@ # module AWS - register_autoloads(self) do - autoload :Errors, 'errors' - end + autoload :Errors, 'aws/errors' + module Core - AWS.register_autoloads(self) do - - autoload :AsyncHandle, 'async_handle' - autoload :Cacheable, 'cacheable' - autoload :Client, 'client' - autoload :Collection, 'collection' - autoload :Configuration, 'configuration' - autoload :CredentialProviders, 'credential_providers' - autoload :Data, 'data' - autoload :IndifferentHash, 'indifferent_hash' - autoload :Inflection, 'inflection' - autoload :JSONParser, 'json_parser' - - autoload :JSONClient, 'json_client' - autoload :JSONRequestBuilder, 'json_request_builder' - autoload :JSONResponseParser, 'json_response_parser' - - autoload :LazyErrorClasses, 'lazy_error_classes' - autoload :LogFormatter, 'log_formatter' - autoload :MetaUtils, 'meta_utils' - autoload :ManagedFile, 'managed_file' - autoload :Model, 'model' - autoload :Naming, 'naming' - autoload :OptionGrammar, 'option_grammar' - autoload :PageResult, 'page_result' - autoload :Policy, 'policy' - - autoload :QueryClient, 'query_client' - autoload :QueryRequestBuilder, 'query_request_builder' - autoload :QueryResponseParser, 'query_response_parser' - - autoload :Resource, 'resource' - autoload :ResourceCache, 'resource_cache' - autoload :Response, 'response' - autoload :ResponseCache, 'response_cache' - - autoload :RESTClient, 'rest_xml_client' - autoload :RESTJSONClient, 'rest_json_client' - autoload :RESTXMLClient, 'rest_xml_client' - autoload :RESTRequestBuilder, 'rest_request_builder' - autoload :RESTResponseParser, 'rest_response_parser' - - autoload :ServiceInterface, 'service_interface' - autoload :Signer, 'signer' - autoload :UriEscape, 'uri_escape' - - end + autoload :AsyncHandle, 'aws/core/async_handle' + autoload :Cacheable, 'aws/core/cacheable' + autoload :Client, 'aws/core/client' + autoload :Collection, 'aws/core/collection' + autoload :Configuration, 'aws/core/configuration' + autoload :CredentialProviders, 'aws/core/credential_providers' + autoload :Data, 'aws/core/data' + autoload :IndifferentHash, 'aws/core/indifferent_hash' + autoload :Inflection, 'aws/core/inflection' + autoload :JSONParser, 'aws/core/json_parser' + + autoload :JSONClient, 'aws/core/json_client' + autoload :JSONRequestBuilder, 'aws/core/json_request_builder' + autoload :JSONResponseParser, 'aws/core/json_response_parser' + + autoload :LazyErrorClasses, 'aws/core/lazy_error_classes' + autoload :LogFormatter, 'aws/core/log_formatter' + autoload :MetaUtils, 'aws/core/meta_utils' + autoload :ManagedFile, 'aws/core/managed_file' + autoload :Model, 'aws/core/model' + autoload :Naming, 'aws/core/naming' + autoload :OptionGrammar, 'aws/core/option_grammar' + autoload :PageResult, 'aws/core/page_result' + autoload :Policy, 'aws/core/policy' + + autoload :QueryClient, 'aws/core/query_client' + autoload :QueryRequestBuilder, 'aws/core/query_request_builder' + autoload :QueryResponseParser, 'aws/core/query_response_parser' + + autoload :Resource, 'aws/core/resource' + autoload :ResourceCache, 'aws/core/resource_cache' + autoload :Response, 'aws/core/response' + autoload :ResponseCache, 'aws/core/response_cache' + + autoload :RESTClient, 'aws/core/rest_xml_client' + autoload :RESTJSONClient, 'aws/core/rest_json_client' + autoload :RESTXMLClient, 'aws/core/rest_xml_client' + autoload :RESTRequestBuilder, 'aws/core/rest_request_builder' + autoload :RESTResponseParser, 'aws/core/rest_response_parser' + + autoload :ServiceInterface, 'aws/core/service_interface' + autoload :Signer, 'aws/core/signer' + autoload :UriEscape, 'aws/core/uri_escape' module Options - AWS.register_autoloads(self) do - autoload :JSONSerializer, 'json_serializer' - autoload :XMLSerializer, 'xml_serializer' - autoload :Validator, 'validator' - end + autoload :JSONSerializer, 'aws/core/options/json_serializer' + autoload :XMLSerializer, 'aws/core/options/xml_serializer' + autoload :Validator, 'aws/core/options/validator' end module Signature - AWS.register_autoloads(self) do - autoload :Version2, 'version_2' - autoload :Version3, 'version_3' - autoload :Version3HTTPS, 'version_3_https' - autoload :Version4, 'version_4' - end + autoload :Version2, 'aws/core/signature/version_2' + autoload :Version3, 'aws/core/signature/version_3' + autoload :Version3HTTPS, 'aws/core/signature/version_3_https' + autoload :Version4, 'aws/core/signature/version_4' end module XML - AWS.register_autoloads(self) do - autoload :Parser, 'parser' - autoload :Grammar, 'grammar' - autoload :Stub, 'stub' - autoload :Frame, 'frame' - autoload :RootFrame, 'root_frame' - autoload :FrameStack, 'frame_stack' - end + + autoload :Parser, 'aws/core/xml/parser' + autoload :Grammar, 'aws/core/xml/grammar' + autoload :Stub, 'aws/core/xml/stub' + autoload :Frame, 'aws/core/xml/frame' + autoload :RootFrame, 'aws/core/xml/root_frame' + autoload :FrameStack, 'aws/core/xml/frame_stack' module SaxHandlers - AWS.register_autoloads(self, 'aws/core/xml/sax_handlers') do - autoload :Nokogiri, 'nokogiri' - autoload :REXML, 'rexml' - end + autoload :Nokogiri, 'aws/core/xml/sax_handlers/nokogiri' + autoload :REXML, 'aws/core/xml/sax_handlers/rexml' end end module Http - AWS.register_autoloads(self) do - autoload :Handler, 'handler' - autoload :NetHttpHandler, 'net_http_handler' - autoload :Request, 'request' - autoload :Response, 'response' - end + autoload :Handler, 'aws/core/http/handler' + autoload :NetHttpHandler, 'aws/core/http/net_http_handler' + autoload :Request, 'aws/core/http/request' + autoload :Response, 'aws/core/http/response' end end diff --git a/lib/aws/core/collection.rb b/lib/aws/core/collection.rb index adaeb40e204..8aaf754bc65 100644 --- a/lib/aws/core/collection.rb +++ b/lib/aws/core/collection.rb @@ -16,11 +16,9 @@ module Core # Provides useful methods for enumerating items in a collection. module Collection - AWS.register_autoloads(self) do - autoload :Simple, 'simple' - autoload :WithNextToken, 'with_next_token' - autoload :WithLimitAndNextToken, 'with_limit_and_next_token' - end + autoload :Simple, 'aws/core/collection/simple' + autoload :WithNextToken, 'aws/core/collection/with_next_token' + autoload :WithLimitAndNextToken, 'aws/core/collection/with_limit_and_next_token' include Enumerable diff --git a/lib/aws/data_pipeline.rb b/lib/aws/data_pipeline.rb index c38394ff596..ef4486daa54 100644 --- a/lib/aws/data_pipeline.rb +++ b/lib/aws/data_pipeline.rb @@ -61,11 +61,9 @@ module AWS # @return [Client] the low-level DataPipeline client object class DataPipeline - AWS.register_autoloads(self, 'aws/data_pipeline') do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Request, 'request' - end + autoload :Client, 'aws/data_pipeline/client' + autoload :Errors, 'aws/data_pipeline/errors' + autoload :Request, 'aws/data_pipeline/request' include Core::ServiceInterface diff --git a/lib/aws/dynamo_db.rb b/lib/aws/dynamo_db.rb index f3363a8cf48..575ae2dc903 100644 --- a/lib/aws/dynamo_db.rb +++ b/lib/aws/dynamo_db.rb @@ -102,25 +102,23 @@ module AWS # @return [Client] the low-level DynamoDB client object class DynamoDB - AWS.register_autoloads(self, 'aws/dynamo_db') do - autoload :AttributeCollection, 'attribute_collection' - autoload :BatchGet, 'batch_get' - autoload :BatchWrite, 'batch_write' - autoload :Binary, 'binary' - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Expectations, 'expectations' - autoload :Item, 'item' - autoload :ItemData, 'item_data' - autoload :ItemCollection, 'item_collection' - autoload :Keys, 'keys' - autoload :PrimaryKeyElement, 'primary_key_element' - autoload :Request, 'request' - autoload :Resource, 'resource' - autoload :Table, 'table' - autoload :TableCollection, 'table_collection' - autoload :Types, 'types' - end + autoload :AttributeCollection, 'aws/dynamo_db/attribute_collection' + autoload :BatchGet, 'aws/dynamo_db/batch_get' + autoload :BatchWrite, 'aws/dynamo_db/batch_write' + autoload :Binary, 'aws/dynamo_db/binary' + autoload :Client, 'aws/dynamo_db/client' + autoload :Errors, 'aws/dynamo_db/errors' + autoload :Expectations, 'aws/dynamo_db/expectations' + autoload :Item, 'aws/dynamo_db/item' + autoload :ItemData, 'aws/dynamo_db/item_data' + autoload :ItemCollection, 'aws/dynamo_db/item_collection' + autoload :Keys, 'aws/dynamo_db/keys' + autoload :PrimaryKeyElement, 'aws/dynamo_db/primary_key_element' + autoload :Request, 'aws/dynamo_db/request' + autoload :Resource, 'aws/dynamo_db/resource' + autoload :Table, 'aws/dynamo_db/table' + autoload :TableCollection, 'aws/dynamo_db/table_collection' + autoload :Types, 'aws/dynamo_db/types' include Core::ServiceInterface diff --git a/lib/aws/ec2.rb b/lib/aws/ec2.rb index cb1c227d1b0..047084d2185 100644 --- a/lib/aws/ec2.rb +++ b/lib/aws/ec2.rb @@ -233,71 +233,68 @@ module AWS # @return [Client] the low-level EC2 client object class EC2 - AWS.register_autoloads(self) do - autoload :Attachment, 'attachment' - autoload :AttachmentCollection, 'attachment_collection' - autoload :AvailabilityZone, 'availability_zone' - autoload :AvailabilityZoneCollection, 'availability_zone_collection' - autoload :BlockDeviceMappings, 'block_device_mappings' - autoload :Client, 'client' - autoload :Collection, 'collection' - autoload :ConfigTransform, 'config_transform' - autoload :CustomerGateway, 'customer_gateway' - autoload :CustomerGatewayCollection, 'customer_gateway_collection' - autoload :DHCPOptions, 'dhcp_options' - autoload :DHCPOptionsCollection, 'dhcp_options_collection' - autoload :ElasticIp, 'elastic_ip' - autoload :ElasticIpCollection, 'elastic_ip_collection' - autoload :Errors, 'errors' - autoload :ExportTask, 'export_task' - autoload :ExportTaskCollection, 'export_task_collection' - autoload :FilteredCollection, 'filtered_collection' - autoload :HasPermissions, 'has_permissions' - autoload :Image, 'image' - autoload :ImageCollection, 'image_collection' - autoload :Instance, 'instance' - autoload :InstanceCollection, 'instance_collection' - autoload :InternetGateway, 'internet_gateway' - autoload :InternetGatewayCollection, 'internet_gateway_collection' - autoload :KeyPair, 'key_pair' - autoload :KeyPairCollection, 'key_pair_collection' - autoload :NetworkACL, 'network_acl' - autoload :NetworkACLCollection, 'network_acl_collection' - autoload :NetworkInterface, 'network_interface' - autoload :NetworkInterfaceCollection, 'network_interface_collection' - autoload :PermissionCollection, 'permission_collection' - autoload :Region, 'region' - autoload :RegionCollection, 'region_collection' - autoload :Request, 'request' - autoload :ReservedInstances, 'reserved_instances' - autoload :ReservedInstancesCollection, 'reserved_instances_collection' - autoload :ReservedInstancesOffering, 'reserved_instances_offering' - autoload :ReservedInstancesOfferingCollection, - 'reserved_instances_offering_collection' - autoload :Resource, 'resource' - autoload :ResourceObject, 'tag_collection' - autoload :ResourceTagCollection, 'resource_tag_collection' - autoload :RouteTable, 'route_table' - autoload :RouteTableCollection, 'route_table_collection' - autoload :SecurityGroup, 'security_group' - autoload :SecurityGroupCollection, 'security_group_collection' - autoload :Snapshot, 'snapshot' - autoload :SnapshotCollection, 'snapshot_collection' - autoload :Subnet, 'subnet' - autoload :SubnetCollection, 'subnet_collection' - autoload :Tag, 'tag' - autoload :TagCollection, 'tag_collection' - autoload :TaggedCollection, 'tagged_collection' - autoload :TaggedItem, 'tagged_item' - autoload :Volume, 'volume' - autoload :VolumeCollection, 'volume_collection' - autoload :VPC, 'vpc' - autoload :VPCCollection, 'vpc_collection' - autoload :VPNConnection, 'vpn_connection' - autoload :VPNConnectionCollection, 'vpn_connection_collection' - autoload :VPNGateway, 'vpn_gateway' - autoload :VPNGatewayCollection, 'vpn_gateway_collection' - end + autoload :Attachment, 'aws/ec2/attachment' + autoload :AttachmentCollection, 'aws/ec2/attachment_collection' + autoload :AvailabilityZone, 'aws/ec2/availability_zone' + autoload :AvailabilityZoneCollection, 'aws/ec2/availability_zone_collection' + autoload :BlockDeviceMappings, 'aws/ec2/block_device_mappings' + autoload :Client, 'aws/ec2/client' + autoload :Collection, 'aws/ec2/collection' + autoload :ConfigTransform, 'aws/ec2/config_transform' + autoload :CustomerGateway, 'aws/ec2/customer_gateway' + autoload :CustomerGatewayCollection, 'aws/ec2/customer_gateway_collection' + autoload :DHCPOptions, 'aws/ec2/dhcp_options' + autoload :DHCPOptionsCollection, 'aws/ec2/dhcp_options_collection' + autoload :ElasticIp, 'aws/ec2/elastic_ip' + autoload :ElasticIpCollection, 'aws/ec2/elastic_ip_collection' + autoload :Errors, 'aws/ec2/errors' + autoload :ExportTask, 'aws/ec2/export_task' + autoload :ExportTaskCollection, 'aws/ec2/export_task_collection' + autoload :FilteredCollection, 'aws/ec2/filtered_collection' + autoload :HasPermissions, 'aws/ec2/has_permissions' + autoload :Image, 'aws/ec2/image' + autoload :ImageCollection, 'aws/ec2/image_collection' + autoload :Instance, 'aws/ec2/instance' + autoload :InstanceCollection, 'aws/ec2/instance_collection' + autoload :InternetGateway, 'aws/ec2/internet_gateway' + autoload :InternetGatewayCollection, 'aws/ec2/internet_gateway_collection' + autoload :KeyPair, 'aws/ec2/key_pair' + autoload :KeyPairCollection, 'aws/ec2/key_pair_collection' + autoload :NetworkACL, 'aws/ec2/network_acl' + autoload :NetworkACLCollection, 'aws/ec2/network_acl_collection' + autoload :NetworkInterface, 'aws/ec2/network_interface' + autoload :NetworkInterfaceCollection, 'aws/ec2/network_interface_collection' + autoload :PermissionCollection, 'aws/ec2/permission_collection' + autoload :Region, 'aws/ec2/region' + autoload :RegionCollection, 'aws/ec2/region_collection' + autoload :Request, 'aws/ec2/request' + autoload :ReservedInstances, 'aws/ec2/reserved_instances' + autoload :ReservedInstancesCollection, 'aws/ec2/reserved_instances_collection' + autoload :ReservedInstancesOffering, 'aws/ec2/reserved_instances_offering' + autoload :ReservedInstancesOfferingCollection, 'aws/ec2/reserved_instances_offering_collection' + autoload :Resource, 'aws/ec2/resource' + autoload :ResourceObject, 'aws/ec2/tag_collection' + autoload :ResourceTagCollection, 'aws/ec2/resource_tag_collection' + autoload :RouteTable, 'aws/ec2/route_table' + autoload :RouteTableCollection, 'aws/ec2/route_table_collection' + autoload :SecurityGroup, 'aws/ec2/security_group' + autoload :SecurityGroupCollection, 'aws/ec2/security_group_collection' + autoload :Snapshot, 'aws/ec2/snapshot' + autoload :SnapshotCollection, 'aws/ec2/snapshot_collection' + autoload :Subnet, 'aws/ec2/subnet' + autoload :SubnetCollection, 'aws/ec2/subnet_collection' + autoload :Tag, 'aws/ec2/tag' + autoload :TagCollection, 'aws/ec2/tag_collection' + autoload :TaggedCollection, 'aws/ec2/tagged_collection' + autoload :TaggedItem, 'aws/ec2/tagged_item' + autoload :Volume, 'aws/ec2/volume' + autoload :VolumeCollection, 'aws/ec2/volume_collection' + autoload :VPC, 'aws/ec2/vpc' + autoload :VPCCollection, 'aws/ec2/vpc_collection' + autoload :VPNConnection, 'aws/ec2/vpn_connection' + autoload :VPNConnectionCollection, 'aws/ec2/vpn_connection_collection' + autoload :VPNGateway, 'aws/ec2/vpn_gateway' + autoload :VPNGatewayCollection, 'aws/ec2/vpn_gateway_collection' include Core::ServiceInterface diff --git a/lib/aws/ec2/security_group.rb b/lib/aws/ec2/security_group.rb index fc8a02446a2..ad35e0574bc 100644 --- a/lib/aws/ec2/security_group.rb +++ b/lib/aws/ec2/security_group.rb @@ -29,12 +29,10 @@ class EC2 # class SecurityGroup < Resource - AWS.register_autoloads(self, 'aws/ec2/security_group') do - autoload :IpPermission, 'ip_permission' - autoload :IpPermissionCollection, 'ip_permission_collection' - autoload :IngressIpPermissionCollection, 'ip_permission_collection' - autoload :EgressIpPermissionCollection, 'ip_permission_collection' - end + autoload :IpPermission, 'aws/ec2/security_group/ip_permission' + autoload :IpPermissionCollection, 'aws/ec2/security_group/ip_permission_collection' + autoload :IngressIpPermissionCollection, 'aws/ec2/security_group/ip_permission_collection' + autoload :EgressIpPermissionCollection, 'aws/ec2/security_group/ip_permission_collection' include TaggedItem diff --git a/lib/aws/elastic_beanstalk.rb b/lib/aws/elastic_beanstalk.rb index 43b40a5285b..412a80a3215 100644 --- a/lib/aws/elastic_beanstalk.rb +++ b/lib/aws/elastic_beanstalk.rb @@ -37,11 +37,9 @@ module AWS # @return [Client] the low-level ElasticBeanstalk client object class ElasticBeanstalk - AWS.register_autoloads(self, 'aws/elastic_beanstalk') do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Request, 'request' - end + autoload :Client, 'aws/elastic_beanstalk/client' + autoload :Errors, 'aws/elastic_beanstalk/errors' + autoload :Request, 'aws/elastic_beanstalk/request' include Core::ServiceInterface diff --git a/lib/aws/elastic_transcoder.rb b/lib/aws/elastic_transcoder.rb index ecb4724edca..32d95aab449 100644 --- a/lib/aws/elastic_transcoder.rb +++ b/lib/aws/elastic_transcoder.rb @@ -18,11 +18,9 @@ module AWS class ElasticTranscoder - AWS.register_autoloads(self, 'aws/elastic_transcoder') do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Request, 'request' - end + autoload :Client, 'aws/elastic_transcoder/client' + autoload :Errors, 'aws/elastic_transcoder/errors' + autoload :Request, 'aws/elastic_transcoder/request' include Core::ServiceInterface diff --git a/lib/aws/elasticache.rb b/lib/aws/elasticache.rb index ccad3269fac..757dd51f8b8 100644 --- a/lib/aws/elasticache.rb +++ b/lib/aws/elasticache.rb @@ -37,11 +37,9 @@ module AWS # @return [Client] the low-level ElastiCache client object class ElastiCache - AWS.register_autoloads(self) do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Request, 'request' - end + autoload :Client, 'aws/elasticache/client' + autoload :Errors, 'aws/elasticache/errors' + autoload :Request, 'aws/elasticache/request' include Core::ServiceInterface diff --git a/lib/aws/elb.rb b/lib/aws/elb.rb index b7fef35c3d1..5e4610112a1 100644 --- a/lib/aws/elb.rb +++ b/lib/aws/elb.rb @@ -38,21 +38,19 @@ module AWS # @return [Client] the low-level ELB client object class ELB - AWS.register_autoloads(self) do - autoload :AvailabilityZoneCollection, 'availability_zone_collection' - autoload :BackendServerPolicyCollection, 'backend_server_policy_collection' - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :InstanceCollection, 'instance_collection' - autoload :ListenerOpts, 'listener_opts' - autoload :Listener, 'listener' - autoload :ListenerCollection, 'listener_collection' - autoload :LoadBalancer, 'load_balancer' - autoload :LoadBalancerCollection, 'load_balancer_collection' - autoload :LoadBalancerPolicy, 'load_balancer_policy' - autoload :LoadBalancerPolicyCollection, 'load_balancer_policy_collection' - autoload :Request, 'request' - end + autoload :AvailabilityZoneCollection, 'aws/elb/availability_zone_collection' + autoload :BackendServerPolicyCollection, 'aws/elb/backend_server_policy_collection' + autoload :Client, 'aws/elb/client' + autoload :Errors, 'aws/elb/errors' + autoload :InstanceCollection, 'aws/elb/instance_collection' + autoload :ListenerOpts, 'aws/elb/listener_opts' + autoload :Listener, 'aws/elb/listener' + autoload :ListenerCollection, 'aws/elb/listener_collection' + autoload :LoadBalancer, 'aws/elb/load_balancer' + autoload :LoadBalancerCollection, 'aws/elb/load_balancer_collection' + autoload :LoadBalancerPolicy, 'aws/elb/load_balancer_policy' + autoload :LoadBalancerPolicyCollection, 'aws/elb/load_balancer_policy_collection' + autoload :Request, 'aws/elb/request' include Core::ServiceInterface diff --git a/lib/aws/emr.rb b/lib/aws/emr.rb index 1f4113088d7..e48655d3283 100644 --- a/lib/aws/emr.rb +++ b/lib/aws/emr.rb @@ -63,15 +63,13 @@ module AWS # @return [Client] the low-level EMR client object class EMR - AWS.register_autoloads(self) do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :InstanceGroup, 'instance_group' - autoload :InstanceGroupCollection, 'instance_group_collection' - autoload :JobFlow, 'job_flow' - autoload :JobFlowCollection, 'job_flow_collection' - autoload :Request, 'request' - end + autoload :Client, 'aws/emr/client' + autoload :Errors, 'aws/emr/errors' + autoload :InstanceGroup, 'aws/emr/instance_group' + autoload :InstanceGroupCollection, 'aws/emr/instance_group_collection' + autoload :JobFlow, 'aws/emr/job_flow' + autoload :JobFlowCollection, 'aws/emr/job_flow_collection' + autoload :Request, 'aws/emr/request' include Core::ServiceInterface diff --git a/lib/aws/glacier.rb b/lib/aws/glacier.rb index 684d18e9387..c873942f56b 100644 --- a/lib/aws/glacier.rb +++ b/lib/aws/glacier.rb @@ -47,17 +47,15 @@ module AWS # @return [Client] the low-level Glacier client object class Glacier - AWS.register_autoloads(self, 'aws/glacier') do - autoload :Archive, 'archive' - autoload :ArchiveCollection, 'archive_collection' - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Request, 'request' - autoload :Resource, 'resource' - autoload :Vault, 'vault' - autoload :VaultCollection, 'vault_collection' - autoload :VaultNotificationConfiguration, 'vault_notification_configuration' - end + autoload :Archive, 'aws/glacier/archive' + autoload :ArchiveCollection, 'aws/glacier/archive_collection' + autoload :Client, 'aws/glacier/client' + autoload :Errors, 'aws/glacier/errors' + autoload :Request, 'aws/glacier/request' + autoload :Resource, 'aws/glacier/resource' + autoload :Vault, 'aws/glacier/vault' + autoload :VaultCollection, 'aws/glacier/vault_collection' + autoload :VaultNotificationConfiguration, 'aws/glacier/vault_notification_configuration' include Core::ServiceInterface diff --git a/lib/aws/iam.rb b/lib/aws/iam.rb index afc34d73a5e..a839550a378 100644 --- a/lib/aws/iam.rb +++ b/lib/aws/iam.rb @@ -136,36 +136,34 @@ module AWS # @return [Client] the low-level IAM client object class IAM - AWS.register_autoloads(self) do - autoload :AccessKey, 'access_key' - autoload :AccessKeyCollection, 'access_key_collection' - autoload :AccountAliasCollection, 'account_alias_collection' - autoload :Client, 'client' - autoload :Collection, 'collection' - autoload :Errors, 'errors' - autoload :Group, 'group' - autoload :GroupCollection, 'group_collection' - autoload :GroupPolicyCollection, 'group_policy_collection' - autoload :GroupUserCollection, 'group_user_collection' - autoload :LoginProfile, 'login_profile' - autoload :MFADevice, 'mfa_device' - autoload :MFADeviceCollection, 'mfa_device_collection' - autoload :Policy, 'policy' - autoload :PolicyCollection, 'policy_collection' - autoload :Request, 'request' - autoload :Resource, 'resource' - autoload :ServerCertificate, 'server_certificate' - autoload :ServerCertificateCollection, 'server_certificate_collection' - autoload :SigningCertificate, 'signing_certificate' - autoload :SigningCertificateCollection, 'signing_certificate_collection' - autoload :User, 'user' - autoload :UserCollection, 'user_collection' - autoload :UserGroupCollection, 'user_group_collection' - autoload :UserPolicy, 'user_policy' - autoload :UserPolicyCollection, 'user_policy_collection' - autoload :VirtualMfaDeviceCollection, 'virtual_mfa_device_collection' - autoload :VirtualMfaDevice, 'virtual_mfa_device' - end + autoload :AccessKey, 'aws/iam/access_key' + autoload :AccessKeyCollection, 'aws/iam/access_key_collection' + autoload :AccountAliasCollection, 'aws/iam/account_alias_collection' + autoload :Client, 'aws/iam/client' + autoload :Collection, 'aws/iam/collection' + autoload :Errors, 'aws/iam/errors' + autoload :Group, 'aws/iam/group' + autoload :GroupCollection, 'aws/iam/group_collection' + autoload :GroupPolicyCollection, 'aws/iam/group_policy_collection' + autoload :GroupUserCollection, 'aws/iam/group_user_collection' + autoload :LoginProfile, 'aws/iam/login_profile' + autoload :MFADevice, 'aws/iam/mfa_device' + autoload :MFADeviceCollection, 'aws/iam/mfa_device_collection' + autoload :Policy, 'aws/iam/policy' + autoload :PolicyCollection, 'aws/iam/policy_collection' + autoload :Request, 'aws/iam/request' + autoload :Resource, 'aws/iam/resource' + autoload :ServerCertificate, 'aws/iam/server_certificate' + autoload :ServerCertificateCollection, 'aws/iam/server_certificate_collection' + autoload :SigningCertificate, 'aws/iam/signing_certificate' + autoload :SigningCertificateCollection, 'aws/iam/signing_certificate_collection' + autoload :User, 'aws/iam/user' + autoload :UserCollection, 'aws/iam/user_collection' + autoload :UserGroupCollection, 'aws/iam/user_group_collection' + autoload :UserPolicy, 'aws/iam/user_policy' + autoload :UserPolicyCollection, 'aws/iam/user_policy_collection' + autoload :VirtualMfaDeviceCollection, 'aws/iam/virtual_mfa_device_collection' + autoload :VirtualMfaDevice, 'aws/iam/virtual_mfa_device' include Core::ServiceInterface diff --git a/lib/aws/iam/collection.rb b/lib/aws/iam/collection.rb index 2b021cff156..4e658b24d9d 100644 --- a/lib/aws/iam/collection.rb +++ b/lib/aws/iam/collection.rb @@ -46,7 +46,7 @@ def with_prefix prefix protected def _each_item marker, max_items, options = {}, &block - + prefix = options.delete(:prefix) || self.prefix options[:path_prefix] = "/#{prefix}".sub(%r{^//}, "/") if prefix diff --git a/lib/aws/import_export.rb b/lib/aws/import_export.rb index d11dd6738ab..b4269a85d83 100644 --- a/lib/aws/import_export.rb +++ b/lib/aws/import_export.rb @@ -61,11 +61,9 @@ module AWS # @return [Client] the low-level ImportExport client object class ImportExport - AWS.register_autoloads(self, 'aws/import_export') do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Request, 'request' - end + autoload :Client, 'aws/import_export/client' + autoload :Errors, 'aws/import_export/errors' + autoload :Request, 'aws/import_export/request' include Core::ServiceInterface diff --git a/lib/aws/ops_works.rb b/lib/aws/ops_works.rb index 69e1b489033..c10b1a24cec 100644 --- a/lib/aws/ops_works.rb +++ b/lib/aws/ops_works.rb @@ -18,11 +18,9 @@ module AWS class OpsWorks - AWS.register_autoloads(self, 'aws/ops_works') do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Request, 'request' - end + autoload :Client, 'aws/ops_works/client' + autoload :Errors, 'aws/ops_works/errors' + autoload :Request, 'aws/ops_works/request' include Core::ServiceInterface diff --git a/lib/aws/rds.rb b/lib/aws/rds.rb index 80489021c83..7889f09e4c1 100644 --- a/lib/aws/rds.rb +++ b/lib/aws/rds.rb @@ -43,15 +43,13 @@ module AWS # @return [Client] the low-level RDS client object class RDS - AWS.register_autoloads(self) do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :DBInstance, 'db_instance' - autoload :DBInstanceCollection, 'db_instance_collection' - autoload :DBSnapshot, 'db_snapshot' - autoload :DBSnapshotCollection, 'db_snapshot_collection' - autoload :Request, 'request' - end + autoload :Client, 'aws/rds/client' + autoload :Errors, 'aws/rds/errors' + autoload :DBInstance, 'aws/rds/db_instance' + autoload :DBInstanceCollection, 'aws/rds/db_instance_collection' + autoload :DBSnapshot, 'aws/rds/db_snapshot' + autoload :DBSnapshotCollection, 'aws/rds/db_snapshot_collection' + autoload :Request, 'aws/rds/request' include Core::ServiceInterface diff --git a/lib/aws/record.rb b/lib/aws/record.rb index 58e30e8eabc..83d9587a0d7 100644 --- a/lib/aws/record.rb +++ b/lib/aws/record.rb @@ -18,15 +18,13 @@ module AWS # AWS::Record is an ORM built on top of AWS services. module Record - AWS.register_autoloads(self) do - autoload :Base, 'model' - autoload :Model, 'model' - autoload :HashModel, 'hash_model' - end + autoload :Base, 'aws/record/model' + autoload :Model, 'aws/record/model' + autoload :HashModel, 'aws/record/hash_model' # @private class RecordNotFound < StandardError; end - + # Sets a prefix to be applied to all SimpleDB domains associated with # AWS::Record::Base classes. # diff --git a/lib/aws/redshift.rb b/lib/aws/redshift.rb index ecda77d947d..7be610f7a8e 100644 --- a/lib/aws/redshift.rb +++ b/lib/aws/redshift.rb @@ -40,11 +40,9 @@ module AWS # class Redshift - AWS.register_autoloads(self) do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Request, 'request' - end + autoload :Client, 'aws/redshift/client' + autoload :Errors, 'aws/redshift/errors' + autoload :Request, 'aws/redshift/request' include Core::ServiceInterface diff --git a/lib/aws/route_53.rb b/lib/aws/route_53.rb index 0a665cb32fb..279baf7f63b 100644 --- a/lib/aws/route_53.rb +++ b/lib/aws/route_53.rb @@ -61,20 +61,18 @@ module AWS # @return [Client] the low-level Route53 client object class Route53 - AWS.register_autoloads(self, 'aws/route_53') do - autoload :ChangeRequest, 'change_batch' - autoload :ChangeBatch, 'change_batch' - autoload :ChangeInfo, 'change_info' - autoload :Client, 'client' - autoload :CreateRequest, 'change_batch' - autoload :DeleteRequest, 'change_batch' - autoload :Errors, 'errors' - autoload :HostedZone, 'hosted_zone' - autoload :HostedZoneCollection, 'hosted_zone_collection' - autoload :Request, 'request' - autoload :ResourceRecordSet, 'resource_record_set' - autoload :ResourceRecordSetCollection, 'resource_record_set_collection' - end + autoload :ChangeRequest, 'aws/route_53/change_batch' + autoload :ChangeBatch, 'aws/route_53/change_batch' + autoload :ChangeInfo, 'aws/route_53/change_info' + autoload :Client, 'aws/route_53/client' + autoload :CreateRequest, 'aws/route_53/change_batch' + autoload :DeleteRequest, 'aws/route_53/change_batch' + autoload :Errors, 'aws/route_53/errors' + autoload :HostedZone, 'aws/route_53/hosted_zone' + autoload :HostedZoneCollection, 'aws/route_53/hosted_zone_collection' + autoload :Request, 'aws/route_53/request' + autoload :ResourceRecordSet, 'aws/route_53/resource_record_set' + autoload :ResourceRecordSetCollection, 'aws/route_53/resource_record_set_collection' include Core::ServiceInterface diff --git a/lib/aws/s3.rb b/lib/aws/s3.rb index 8e65ce2ee12..ac80780d96f 100644 --- a/lib/aws/s3.rb +++ b/lib/aws/s3.rb @@ -107,41 +107,39 @@ module AWS # class S3 - AWS.register_autoloads(self) do - autoload :AccessControlList, 'access_control_list' - autoload :ACLObject, 'acl_object' - autoload :ACLOptions, 'acl_options' - autoload :Bucket, 'bucket' - autoload :BucketCollection, 'bucket_collection' - autoload :BucketTagCollection, 'bucket_tag_collection' - autoload :BucketLifecycleConfiguration, 'bucket_lifecycle_configuration' - autoload :BucketVersionCollection, 'bucket_version_collection' - autoload :Client, 'client' - autoload :CORSRule, 'cors_rule' - autoload :CORSRuleCollection, 'cors_rule_collection' - autoload :DataOptions, 'data_options' - autoload :EncryptionUtils, 'encryption_utils' - autoload :CipherIO, 'cipher_io' - autoload :Errors, 'errors' - autoload :MultipartUpload, 'multipart_upload' - autoload :MultipartUploadCollection, 'multipart_upload_collection' - autoload :ObjectCollection, 'object_collection' - autoload :ObjectMetadata, 'object_metadata' - autoload :ObjectUploadCollection, 'object_upload_collection' - autoload :ObjectVersion, 'object_version' - autoload :ObjectVersionCollection, 'object_version_collection' - autoload :PaginatedCollection, 'paginated_collection' - autoload :Policy, 'policy' - autoload :PrefixAndDelimiterCollection, 'prefix_and_delimiter_collection' - autoload :PrefixedCollection, 'prefixed_collection' - autoload :PresignedPost, 'presigned_post' - autoload :Request, 'request' - autoload :S3Object, 's3_object' - autoload :Tree, 'tree' - autoload :UploadedPart, 'uploaded_part' - autoload :UploadedPartCollection, 'uploaded_part_collection' - autoload :WebsiteConfiguration, 'website_configuration' - end + autoload :AccessControlList, 'aws/s3/access_control_list' + autoload :ACLObject, 'aws/s3/acl_object' + autoload :ACLOptions, 'aws/s3/acl_options' + autoload :Bucket, 'aws/s3/bucket' + autoload :BucketCollection, 'aws/s3/bucket_collection' + autoload :BucketTagCollection, 'aws/s3/bucket_tag_collection' + autoload :BucketLifecycleConfiguration, 'aws/s3/bucket_lifecycle_configuration' + autoload :BucketVersionCollection, 'aws/s3/bucket_version_collection' + autoload :Client, 'aws/s3/client' + autoload :CORSRule, 'aws/s3/cors_rule' + autoload :CORSRuleCollection, 'aws/s3/cors_rule_collection' + autoload :DataOptions, 'aws/s3/data_options' + autoload :EncryptionUtils, 'aws/s3/encryption_utils' + autoload :CipherIO, 'aws/s3/cipher_io' + autoload :Errors, 'aws/s3/errors' + autoload :MultipartUpload, 'aws/s3/multipart_upload' + autoload :MultipartUploadCollection, 'aws/s3/multipart_upload_collection' + autoload :ObjectCollection, 'aws/s3/object_collection' + autoload :ObjectMetadata, 'aws/s3/object_metadata' + autoload :ObjectUploadCollection, 'aws/s3/object_upload_collection' + autoload :ObjectVersion, 'aws/s3/object_version' + autoload :ObjectVersionCollection, 'aws/s3/object_version_collection' + autoload :PaginatedCollection, 'aws/s3/paginated_collection' + autoload :Policy, 'aws/s3/policy' + autoload :PrefixAndDelimiterCollection, 'aws/s3/prefix_and_delimiter_collection' + autoload :PrefixedCollection, 'aws/s3/prefixed_collection' + autoload :PresignedPost, 'aws/s3/presigned_post' + autoload :Request, 'aws/s3/request' + autoload :S3Object, 'aws/s3/s3_object' + autoload :Tree, 'aws/s3/tree' + autoload :UploadedPart, 'aws/s3/uploaded_part' + autoload :UploadedPartCollection, 'aws/s3/uploaded_part_collection' + autoload :WebsiteConfiguration, 'aws/s3/website_configuration' include Core::ServiceInterface diff --git a/lib/aws/s3/client.rb b/lib/aws/s3/client.rb index 1ecdf5c016a..246efa543d4 100644 --- a/lib/aws/s3/client.rb +++ b/lib/aws/s3/client.rb @@ -29,9 +29,7 @@ class Client < Core::Client XMLNS = "http://s3.amazonaws.com/doc/#{API_VERSION}/" - AWS.register_autoloads(self) do - autoload :XML, 'xml' - end + autoload :XML, 'aws/s3/client/xml' # @private EMPTY_BODY_ERRORS = { diff --git a/lib/aws/s3/tree.rb b/lib/aws/s3/tree.rb index 64e7b3ecb95..2254a14151e 100644 --- a/lib/aws/s3/tree.rb +++ b/lib/aws/s3/tree.rb @@ -77,13 +77,11 @@ class S3 # using. class Tree - AWS.register_autoloads(self) do - autoload :BranchNode, 'branch_node' - autoload :ChildCollection, 'child_collection' - autoload :LeafNode, 'leaf_node' - autoload :Node, 'node' - autoload :Parent, 'parent' - end + autoload :BranchNode, 'aws/s3/tree/branch_node' + autoload :ChildCollection, 'aws/s3/tree/child_collection' + autoload :LeafNode, 'aws/s3/tree/leaf_node' + autoload :Node, 'aws/s3/tree/node' + autoload :Parent, 'aws/s3/tree/parent' include Parent diff --git a/lib/aws/simple_db.rb b/lib/aws/simple_db.rb index 7275b0f8573..6a7fa274ac7 100644 --- a/lib/aws/simple_db.rb +++ b/lib/aws/simple_db.rb @@ -140,23 +140,21 @@ module AWS # @return [Client] the low-level SimpleDB client object class SimpleDB - AWS.register_autoloads(self, 'aws/simple_db') do - autoload :Attribute, 'attribute' - autoload :AttributeCollection, 'attribute_collection' - autoload :Client, 'client' - autoload :ConsistentReadOption, 'consistent_read_option' - autoload :DeleteAttributes, 'delete_attributes' - autoload :Domain, 'domain' - autoload :DomainCollection, 'domain_collection' - autoload :DomainMetadata, 'domain_metadata' - autoload :Errors, 'errors' - autoload :ExpectConditionOption, 'expect_condition_option' - autoload :Item, 'item' - autoload :ItemCollection, 'item_collection' - autoload :ItemData, 'item_data' - autoload :PutAttributes, 'put_attributes' - autoload :Request, 'request' - end + autoload :Attribute, 'aws/simple_db/attribute' + autoload :AttributeCollection, 'aws/simple_db/attribute_collection' + autoload :Client, 'aws/simple_db/client' + autoload :ConsistentReadOption, 'aws/simple_db/consistent_read_option' + autoload :DeleteAttributes, 'aws/simple_db/delete_attributes' + autoload :Domain, 'aws/simple_db/domain' + autoload :DomainCollection, 'aws/simple_db/domain_collection' + autoload :DomainMetadata, 'aws/simple_db/domain_metadata' + autoload :Errors, 'aws/simple_db/errors' + autoload :ExpectConditionOption, 'aws/simple_db/expect_condition_option' + autoload :Item, 'aws/simple_db/item' + autoload :ItemCollection, 'aws/simple_db/item_collection' + autoload :ItemData, 'aws/simple_db/item_data' + autoload :PutAttributes, 'aws/simple_db/put_attributes' + autoload :Request, 'aws/simple_db/request' include Core::ServiceInterface diff --git a/lib/aws/simple_email_service.rb b/lib/aws/simple_email_service.rb index dc0a28778f9..51b52bd0189 100644 --- a/lib/aws/simple_email_service.rb +++ b/lib/aws/simple_email_service.rb @@ -172,15 +172,13 @@ module AWS # @return [Client] the low-level SimpleEmailService client object class SimpleEmailService - AWS.register_autoloads(self, 'aws/simple_email_service') do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :EmailAddressCollection, 'email_address_collection' - autoload :Identity, 'identity' - autoload :IdentityCollection, 'identity_collection' - autoload :Quotas, 'quotas' - autoload :Request, 'request' - end + autoload :Client, 'aws/simple_email_service/client' + autoload :Errors, 'aws/simple_email_service/errors' + autoload :EmailAddressCollection, 'aws/simple_email_service/email_address_collection' + autoload :Identity, 'aws/simple_email_service/identity' + autoload :IdentityCollection, 'aws/simple_email_service/identity_collection' + autoload :Quotas, 'aws/simple_email_service/quotas' + autoload :Request, 'aws/simple_email_service/request' include Core::ServiceInterface diff --git a/lib/aws/simple_workflow.rb b/lib/aws/simple_workflow.rb index 29f361992ef..423dddb3ccd 100644 --- a/lib/aws/simple_workflow.rb +++ b/lib/aws/simple_workflow.rb @@ -192,30 +192,28 @@ module AWS # @return [Client] the low-level SimpleWorkflow client object class SimpleWorkflow - AWS.register_autoloads(self, 'aws/simple_workflow') do - autoload :ActivityType, 'activity_type' - autoload :ActivityTypeCollection, 'activity_type_collection' - autoload :ActivityTask, 'activity_task' - autoload :ActivityTaskCollection, 'activity_task_collection' - autoload :Client, 'client' - autoload :Count, 'count' - autoload :DecisionTask, 'decision_task' - autoload :DecisionTaskCollection, 'decision_task_collection' - autoload :Domain, 'domain' - autoload :DomainCollection, 'domain_collection' - autoload :Errors, 'errors' - autoload :HistoryEvent, 'history_event' - autoload :HistoryEventCollection, 'history_event_collection' - autoload :OptionFormatters, 'option_formatters' - autoload :Request, 'request' - autoload :Resource, 'resource' - autoload :Type, 'type' - autoload :TypeCollection, 'type_collection' - autoload :WorkflowExecution, 'workflow_execution' - autoload :WorkflowExecutionCollection, 'workflow_execution_collection' - autoload :WorkflowType, 'workflow_type' - autoload :WorkflowTypeCollection, 'workflow_type_collection' - end + autoload :ActivityType, 'aws/simple_workflow/activity_type' + autoload :ActivityTypeCollection, 'aws/simple_workflow/activity_type_collection' + autoload :ActivityTask, 'aws/simple_workflow/activity_task' + autoload :ActivityTaskCollection, 'aws/simple_workflow/activity_task_collection' + autoload :Client, 'aws/simple_workflow/client' + autoload :Count, 'aws/simple_workflow/count' + autoload :DecisionTask, 'aws/simple_workflow/decision_task' + autoload :DecisionTaskCollection, 'aws/simple_workflow/decision_task_collection' + autoload :Domain, 'aws/simple_workflow/domain' + autoload :DomainCollection, 'aws/simple_workflow/domain_collection' + autoload :Errors, 'aws/simple_workflow/errors' + autoload :HistoryEvent, 'aws/simple_workflow/history_event' + autoload :HistoryEventCollection, 'aws/simple_workflow/history_event_collection' + autoload :OptionFormatters, 'aws/simple_workflow/option_formatters' + autoload :Request, 'aws/simple_workflow/request' + autoload :Resource, 'aws/simple_workflow/resource' + autoload :Type, 'aws/simple_workflow/type' + autoload :TypeCollection, 'aws/simple_workflow/type_collection' + autoload :WorkflowExecution, 'aws/simple_workflow/workflow_execution' + autoload :WorkflowExecutionCollection, 'aws/simple_workflow/workflow_execution_collection' + autoload :WorkflowType, 'aws/simple_workflow/workflow_type' + autoload :WorkflowTypeCollection, 'aws/simple_workflow/workflow_type_collection' include Core::ServiceInterface diff --git a/lib/aws/sns.rb b/lib/aws/sns.rb index c1aba66988f..d8530ef408f 100644 --- a/lib/aws/sns.rb +++ b/lib/aws/sns.rb @@ -45,18 +45,16 @@ module AWS # @return [Client] the low-level SNS client object class SNS - AWS.register_autoloads(self) do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Policy, 'policy' - autoload :HasDeliveryPolicy, 'has_delivery_policy' - autoload :Request, 'request' - autoload :Subscription, 'subscription' - autoload :SubscriptionCollection, 'subscription_collection' - autoload :Topic, 'topic' - autoload :TopicCollection, 'topic_collection' - autoload :TopicSubscriptionCollection, 'topic_subscription_collection' - end + autoload :Client, 'aws/sns/client' + autoload :Errors, 'aws/sns/errors' + autoload :Policy, 'aws/sns/policy' + autoload :HasDeliveryPolicy, 'aws/sns/has_delivery_policy' + autoload :Request, 'aws/sns/request' + autoload :Subscription, 'aws/sns/subscription' + autoload :SubscriptionCollection, 'aws/sns/subscription_collection' + autoload :Topic, 'aws/sns/topic' + autoload :TopicCollection, 'aws/sns/topic_collection' + autoload :TopicSubscriptionCollection, 'aws/sns/topic_subscription_collection' include Core::ServiceInterface diff --git a/lib/aws/sqs.rb b/lib/aws/sqs.rb index 86fdabb5bff..6a5efc7f7c7 100644 --- a/lib/aws/sqs.rb +++ b/lib/aws/sqs.rb @@ -57,16 +57,14 @@ module AWS # @return [Client] the low-level SQS client object class SQS - AWS.register_autoloads(self) do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Queue, 'queue' - autoload :QueueCollection, 'queue_collection' - autoload :Policy, 'policy' - autoload :ReceivedMessage, 'received_message' - autoload :ReceivedSNSMessage, 'received_sns_message' - autoload :Request, 'request' - end + autoload :Client, 'aws/sqs/client' + autoload :Errors, 'aws/sqs/errors' + autoload :Queue, 'aws/sqs/queue' + autoload :QueueCollection, 'aws/sqs/queue_collection' + autoload :Policy, 'aws/sqs/policy' + autoload :ReceivedMessage, 'aws/sqs/received_message' + autoload :ReceivedSNSMessage, 'aws/sqs/received_sns_message' + autoload :Request, 'aws/sqs/request' include Core::ServiceInterface diff --git a/lib/aws/storage_gateway.rb b/lib/aws/storage_gateway.rb index a1ac851005f..ea754d547c4 100644 --- a/lib/aws/storage_gateway.rb +++ b/lib/aws/storage_gateway.rb @@ -61,11 +61,9 @@ module AWS # @return [Client] the low-level StorageGateway client object class StorageGateway - AWS.register_autoloads(self, 'aws/storage_gateway') do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :Request, 'request' - end + autoload :Client, 'aws/storage_gateway/client' + autoload :Errors, 'aws/storage_gateway/errors' + autoload :Request, 'aws/storage_gateway/request' include Core::ServiceInterface diff --git a/lib/aws/sts.rb b/lib/aws/sts.rb index ad095524bdb..e4839441a3f 100644 --- a/lib/aws/sts.rb +++ b/lib/aws/sts.rb @@ -41,14 +41,12 @@ module AWS # @return [Client] the low-level STS client object class STS - AWS.register_autoloads(self) do - autoload :Client, 'client' - autoload :Errors, 'errors' - autoload :FederatedSession, 'federated_session' - autoload :Policy, 'policy' - autoload :Request, 'request' - autoload :Session, 'session' - end + autoload :Client, 'aws/sts/client' + autoload :Errors, 'aws/sts/errors' + autoload :FederatedSession, 'aws/sts/federated_session' + autoload :Policy, 'aws/sts/policy' + autoload :Request, 'aws/sts/request' + autoload :Session, 'aws/sts/session' include Core::ServiceInterface From 221ef12638aed0ef8050f881a4f6898ecebbc704 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Fri, 22 Mar 2013 08:35:21 -0700 Subject: [PATCH 0482/1398] Removed the legacy autoloader module. --- lib/aws/core/autoloader.rb | 64 -------------------------------------- 1 file changed, 64 deletions(-) delete mode 100644 lib/aws/core/autoloader.rb diff --git a/lib/aws/core/autoloader.rb b/lib/aws/core/autoloader.rb deleted file mode 100644 index 94a775ced52..00000000000 --- a/lib/aws/core/autoloader.rb +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. - -require 'set' - -module AWS - - @@eager = false - @@autoloads = {} - - def self.register_autoloads klass, prefix = nil, &block - autoloader = Core::Autoloader.new(klass, prefix) - autoloader.instance_eval(&block) - autoloader.autoloads.each_pair do |const_name, file_path| - require(file_path) if @@eager - @@autoloads["#{klass}::#{const_name}"] = file_path - end - end - - def self.eager_autoload! - unless @@eager - @@eager = true - @@autoloads.values.uniq.each {|file_path| require(file_path) } - end - end - - def self.autoloads - @@autoloads - end - - module Core - - # @private - class Autoloader - - def initialize klass, prefix = nil - @klass = klass - @prefix = prefix || klass.name.gsub(/::/, '/').downcase - @autoloads = {} - end - - attr_reader :autoloads - - def autoload const_name, file_name - path = "#{@prefix}/#{file_name}" - @klass.autoload(const_name, path) - @autoloads[const_name] = path - end - - end - - end - -end From ba27117841965937ab4e1cfc4ba4f285f7f54928 Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Mon, 25 Mar 2013 17:19:45 -0700 Subject: [PATCH 0483/1398] Updated AWS::Record to use autoload instead of explicit require statements to support eager autoloading. --- lib/aws/record.rb | 30 ++++++++++++++++++++++- lib/aws/record/abstract_base.rb | 8 ------ lib/aws/record/hash_model.rb | 9 +++---- lib/aws/record/hash_model/attributes.rb | 2 -- lib/aws/record/model.rb | 8 +++--- lib/aws/record/model/attributes.rb | 2 -- lib/aws/record/validations.rb | 12 --------- lib/aws/record/validators/acceptance.rb | 2 -- lib/aws/record/validators/block.rb | 2 -- lib/aws/record/validators/confirmation.rb | 2 -- lib/aws/record/validators/count.rb | 2 -- lib/aws/record/validators/exclusion.rb | 4 +-- lib/aws/record/validators/format.rb | 2 -- lib/aws/record/validators/inclusion.rb | 4 +-- lib/aws/record/validators/length.rb | 2 -- lib/aws/record/validators/method.rb | 2 -- lib/aws/record/validators/numericality.rb | 2 -- lib/aws/record/validators/presence.rb | 2 -- 18 files changed, 39 insertions(+), 58 deletions(-) diff --git a/lib/aws/record.rb b/lib/aws/record.rb index 83d9587a0d7..4b175287d6d 100644 --- a/lib/aws/record.rb +++ b/lib/aws/record.rb @@ -18,9 +18,37 @@ module AWS # AWS::Record is an ORM built on top of AWS services. module Record + autoload :AbstractBase, 'aws/record/abstract_base' + autoload :Attributes, 'aws/record/attributes' autoload :Base, 'aws/record/model' - autoload :Model, 'aws/record/model' + autoload :Conversion, 'aws/record/conversion' + autoload :DirtyTracking, 'aws/record/dirty_tracking' + autoload :Errors, 'aws/record/errors' + autoload :Exceptions, 'aws/record/exceptions' autoload :HashModel, 'aws/record/hash_model' + autoload :Model, 'aws/record/model' + autoload :Naming, 'aws/record/naming' + autoload :Scope, 'aws/record/scope' + autoload :Validations, 'aws/record/validations' + + # errors + autoload :InvalidRecordError, 'aws/record/exceptions' + autoload :EmptyRecordError, 'aws/record/exceptions' + autoload :UndefinedAttributeError, 'aws/record/exceptions' + + # validators + autoload :AcceptanceValidator, 'aws/record/validators/acceptance' + autoload :BlockValidator, 'aws/record/validators/block' + autoload :ConfirmationValidator, 'aws/record/validators/confirmation' + autoload :CountValidator, 'aws/record/validators/count' + autoload :ExclusionValidator, 'aws/record/validators/exclusion' + autoload :FormatValidator, 'aws/record/validators/format' + autoload :InclusionValidator, 'aws/record/validators/inclusion' + autoload :LengthValidator, 'aws/record/validators/length' + autoload :MethodValidator, 'aws/record/validators/method' + autoload :NumericalityValidator, 'aws/record/validators/numericality' + autoload :PresenceValidator, 'aws/record/validators/presence' + autoload :Validator, 'aws/record/validator' # @private class RecordNotFound < StandardError; end diff --git a/lib/aws/record/abstract_base.rb b/lib/aws/record/abstract_base.rb index a5d375265f4..2c940650f0d 100644 --- a/lib/aws/record/abstract_base.rb +++ b/lib/aws/record/abstract_base.rb @@ -14,14 +14,6 @@ require 'uuidtools' require 'set' -require 'aws/record/scope' -require 'aws/record/naming' -require 'aws/record/validations' -require 'aws/record/dirty_tracking' -require 'aws/record/conversion' -require 'aws/record/errors' -require 'aws/record/exceptions' - module AWS module Record module AbstractBase diff --git a/lib/aws/record/hash_model.rb b/lib/aws/record/hash_model.rb index f4f7e55df55..2cf1f31e724 100644 --- a/lib/aws/record/hash_model.rb +++ b/lib/aws/record/hash_model.rb @@ -11,15 +11,14 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/abstract_base' -require 'aws/record/hash_model/scope' -require 'aws/record/hash_model/attributes' -require 'aws/record/hash_model/finder_methods' - module AWS module Record class HashModel + require 'aws/record/hash_model/attributes' + require 'aws/record/hash_model/finder_methods' + require 'aws/record/hash_model/scope' + extend AbstractBase class << self diff --git a/lib/aws/record/hash_model/attributes.rb b/lib/aws/record/hash_model/attributes.rb index a91dd8aa363..108e901988c 100644 --- a/lib/aws/record/hash_model/attributes.rb +++ b/lib/aws/record/hash_model/attributes.rb @@ -11,8 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/attributes.rb' - module AWS module Record class HashModel diff --git a/lib/aws/record/model.rb b/lib/aws/record/model.rb index bcc293816c9..cc41a2c1e1a 100644 --- a/lib/aws/record/model.rb +++ b/lib/aws/record/model.rb @@ -13,10 +13,6 @@ # todo move these to included modules (like validations and naming) -require 'aws/record/abstract_base' -require 'aws/record/model/scope' -require 'aws/record/model/attributes' -require 'aws/record/model/finder_methods' module AWS module Record @@ -271,6 +267,10 @@ module Record # class Model + require 'aws/record/model/attributes' + require 'aws/record/model/finder_methods' + require 'aws/record/model/scope' + extend AbstractBase class << self diff --git a/lib/aws/record/model/attributes.rb b/lib/aws/record/model/attributes.rb index 4579b1889fa..6354c25b04e 100644 --- a/lib/aws/record/model/attributes.rb +++ b/lib/aws/record/model/attributes.rb @@ -11,8 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/attributes.rb' - module AWS module Record class Model diff --git a/lib/aws/record/validations.rb b/lib/aws/record/validations.rb index 14c7ce34518..b215dfbe7a3 100644 --- a/lib/aws/record/validations.rb +++ b/lib/aws/record/validations.rb @@ -11,18 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/validators/acceptance' -require 'aws/record/validators/block' -require 'aws/record/validators/confirmation' -require 'aws/record/validators/count' -require 'aws/record/validators/exclusion' -require 'aws/record/validators/format' -require 'aws/record/validators/inclusion' -require 'aws/record/validators/length' -require 'aws/record/validators/numericality' -require 'aws/record/validators/presence' -require 'aws/record/validators/method' - module AWS module Record diff --git a/lib/aws/record/validators/acceptance.rb b/lib/aws/record/validators/acceptance.rb index 3acdbf85b9e..6e1d61abb70 100644 --- a/lib/aws/record/validators/acceptance.rb +++ b/lib/aws/record/validators/acceptance.rb @@ -11,8 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/validator' - module AWS module Record diff --git a/lib/aws/record/validators/block.rb b/lib/aws/record/validators/block.rb index 1a3d4533107..6c08ecc784b 100644 --- a/lib/aws/record/validators/block.rb +++ b/lib/aws/record/validators/block.rb @@ -11,8 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/validator' - module AWS module Record diff --git a/lib/aws/record/validators/confirmation.rb b/lib/aws/record/validators/confirmation.rb index f3dfb0ac5ba..a9adf15ae8c 100644 --- a/lib/aws/record/validators/confirmation.rb +++ b/lib/aws/record/validators/confirmation.rb @@ -11,8 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/validator' - module AWS module Record diff --git a/lib/aws/record/validators/count.rb b/lib/aws/record/validators/count.rb index 975f9babe8c..431026e53a9 100644 --- a/lib/aws/record/validators/count.rb +++ b/lib/aws/record/validators/count.rb @@ -11,8 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/validator' - module AWS module Record diff --git a/lib/aws/record/validators/exclusion.rb b/lib/aws/record/validators/exclusion.rb index eb5e16699b6..35bad868824 100644 --- a/lib/aws/record/validators/exclusion.rb +++ b/lib/aws/record/validators/exclusion.rb @@ -11,11 +11,9 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/validators/inclusion' - module AWS module Record - + # @private class ExclusionValidator < InclusionValidator diff --git a/lib/aws/record/validators/format.rb b/lib/aws/record/validators/format.rb index 39fcdad942c..8bd68b3dcaa 100644 --- a/lib/aws/record/validators/format.rb +++ b/lib/aws/record/validators/format.rb @@ -11,8 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/validator' - module AWS module Record diff --git a/lib/aws/record/validators/inclusion.rb b/lib/aws/record/validators/inclusion.rb index e11cac0c153..ecd20439fe6 100644 --- a/lib/aws/record/validators/inclusion.rb +++ b/lib/aws/record/validators/inclusion.rb @@ -11,11 +11,9 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/validator' - module AWS module Record - + # @private class InclusionValidator < Validator diff --git a/lib/aws/record/validators/length.rb b/lib/aws/record/validators/length.rb index 455828b9fc2..1dacb50b29e 100644 --- a/lib/aws/record/validators/length.rb +++ b/lib/aws/record/validators/length.rb @@ -11,8 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/validator' - module AWS module Record diff --git a/lib/aws/record/validators/method.rb b/lib/aws/record/validators/method.rb index 0834bee909e..ccb32e0c099 100644 --- a/lib/aws/record/validators/method.rb +++ b/lib/aws/record/validators/method.rb @@ -11,8 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/validator' - module AWS module Record diff --git a/lib/aws/record/validators/numericality.rb b/lib/aws/record/validators/numericality.rb index 9153d808cfb..7be7f2d9065 100644 --- a/lib/aws/record/validators/numericality.rb +++ b/lib/aws/record/validators/numericality.rb @@ -11,8 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/validator' - module AWS module Record diff --git a/lib/aws/record/validators/presence.rb b/lib/aws/record/validators/presence.rb index 5c809607fde..58b7c5952c7 100644 --- a/lib/aws/record/validators/presence.rb +++ b/lib/aws/record/validators/presence.rb @@ -11,8 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -require 'aws/record/validator' - module AWS module Record From 03cceadef1a1f3febe897b1155f7d14186b60e3b Mon Sep 17 00:00:00 2001 From: Loren Segal Date: Tue, 26 Mar 2013 00:50:45 -0700 Subject: [PATCH 0484/1398] Refactor duplicate attachment code in EC2 --- lib/aws/ec2/attachment.rb | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/lib/aws/ec2/attachment.rb b/lib/aws/ec2/attachment.rb index 99778071d47..8dbceaed734 100644 --- a/lib/aws/ec2/attachment.rb +++ b/lib/aws/ec2/attachment.rb @@ -67,16 +67,7 @@ def initialize volume, instance, device, options = {} attribute :delete_on_termination, :boolean => true populates_from(:describe_volumes) do |resp| - if volume = resp.volume_index[self.volume.id] and - attachments = volume.attachment_set and - attachment = attachments.find do |att| - att.instance_id == self.instance.id && - att.volume_id == self.volume.id && - att.device == self.device - end - then - attachment - end + find_attachment(resp) end populates_from(:attach_volume, :detach_volume) do |resp| @@ -124,14 +115,17 @@ def describe_call private def describe_attachment - (resp = describe_call and - volume = resp.volume_index[self.volume.id] and - attachments = volume.attachment_set and - attachment = attachments.find do |att| - att.instance_id == self.instance.id && - att.volume_id == self.volume.id && - att.device == self.device - end) or nil + find_attachment(describe_call) + end + + def find_attachment(resp) + vol = resp.volume_index[volume.id] and + attachments = vol.attachment_set and + attachments.find do |att| + att.instance_id == instance.id && + att.volume_id == volume.id && + att.device == device + end end end From 34ec9a9a375b38a61c82cb8a4e28ecbd363cc235 Mon Sep 17 00:00:00 2001 From: James Miller Date: Fri, 29 Mar 2013 16:53:34 -0700 Subject: [PATCH 0485/1398] Spelling error in load_balancer_collection.rb --- lib/aws/elb/load_balancer_collection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aws/elb/load_balancer_collection.rb b/lib/aws/elb/load_balancer_collection.rb index be6b800eb04..59d99db1e49 100644 --- a/lib/aws/elb/load_balancer_collection.rb +++ b/lib/aws/elb/load_balancer_collection.rb @@ -60,7 +60,7 @@ class LoadBalancerCollection # # @option options [String,IAM::ServerCertificate] :server_certificate (nil) # The ARN string of an IAM::ServerCertifcate or an - # IAM::ServerCertificate object. Reqruied for HTTPs listeners. + # IAM::ServerCertificate object. Required for HTTPs listeners. # # @option options [Array] :subnets An list of VPC subets to attach the # load balancer to. This can be an array of subnet ids (strings) or From 438da7daf0544458046185b7c105abd1a557b30a Mon Sep 17 00:00:00 2001 From: Trevor Rowe Date: Mon, 25 Mar 2013 20:08:15 -0700 Subject: [PATCH 0486/1398] Removed trailing whitespace from the end of lines. --- .../launch_configurations.feature | 2 +- .../step_definitions/auto_scaling.rb | 2 +- .../notification_configurations.rb | 2 +- .../step_definitions/scaling_policies.rb | 4 +- .../step_definitions/stacks.rb | 4 +- features/cloud_formation/templates.feature | 6 +- .../cloud_front/step_definitions/client.rb | 2 +- .../cloud_watch/step_definitions/metrics.rb | 2 +- features/dynamo_db/step_definitions/tables.rb | 2 +- features/ec2/customer_gateways.feature | 6 +- features/ec2/images.feature | 2 +- features/ec2/internet_gateways.feature | 4 +- features/ec2/route_tables.feature | 10 +- features/ec2/security_groups.feature | 2 +- features/ec2/step_definitions/ec2.rb | 6 +- .../step_definitions/network_interfaces.rb | 4 +- features/ec2/step_definitions/route_tables.rb | 2 +- .../ec2/step_definitions/security_groups.rb | 4 +- .../step_definitions/snapshot_attributes.rb | 2 +- features/ec2/subnets.feature | 2 +- features/ec2/vpn_gateways.feature | 4 +- features/elb/backend_server_policies.feature | 2 +- features/elb/listeners.feature | 6 +- features/elb/load_balancers.feature | 8 +- .../backend_server_policies.rb | 8 +- features/elb/step_definitions/instances.rb | 2 +- features/elb/step_definitions/listeners.rb | 8 +- .../elb/step_definitions/load_balancers.rb | 4 +- features/elb/step_definitions/policies.rb | 2 +- features/iam/account_aliases.feature | 4 +- features/iam/signing_certificates.feature | 4 +- features/iam/step_definitions/iam.rb | 2 +- .../step_definitions/signing_certificates.rb | 10 +- features/iam/users.feature | 2 +- features/record/count.feature | 2 +- features/record/find.feature | 2 +- features/record/optimistic_locking.feature | 6 +- features/record/scope.feature | 4 +- features/record/validations.feature | 2 +- .../lifecycle_configuration.feature | 2 +- .../lifecycle_configuration.rb | 2 +- .../s3/high_level/step_definitions/post.rb | 4 +- features/s3/high_level/tree.feature | 12 +- .../s3/low_level/step_definitions/versions.rb | 4 +- features/simple_db/high_level/items.feature | 6 +- .../high_level/step_definitions/items.rb | 6 +- .../high_level/step_definitions/select.rb | 4 +- .../email_addresses.feature | 8 +- features/simple_email_service/send.feature | 4 +- .../step_definitions/send.rb | 2 +- .../step_definitions/ses.rb | 2 +- .../simple_workflow/activity_types.feature | 4 +- .../step_definitions/activity_types.rb | 2 +- .../step_definitions/decision_tasks.rb | 4 +- .../step_definitions/workflow_executions.rb | 10 +- .../step_definitions/workflow_types.rb | 2 +- .../simple_workflow/workflow_types.feature | 4 +- features/sns/step_definitions/topics.rb | 2 +- features/sns/topics.feature | 2 +- features/sqs/queues.feature | 2 +- features/sqs/step_definitions/policies.rb | 4 +- features/step_definitions.rb | 4 +- features/support/common.rb | 4 +- lib/aws/auto_scaling/activity.rb | 2 +- lib/aws/auto_scaling/activity_collection.rb | 12 +- lib/aws/auto_scaling/group.rb | 42 ++-- lib/aws/auto_scaling/group_collection.rb | 12 +- lib/aws/auto_scaling/group_options.rb | 28 +-- lib/aws/auto_scaling/instance.rb | 30 +-- lib/aws/auto_scaling/instance_collection.rb | 8 +- lib/aws/auto_scaling/launch_configuration.rb | 6 +- .../notification_configuration_collection.rb | 20 +- lib/aws/auto_scaling/scaling_policy.rb | 6 +- .../auto_scaling/scaling_policy_collection.rb | 12 +- .../auto_scaling/scaling_policy_options.rb | 8 +- lib/aws/auto_scaling/scheduled_action.rb | 6 +- .../scheduled_action_collection.rb | 30 +-- lib/aws/auto_scaling/tag_collection.rb | 6 +- lib/aws/cloud_formation.rb | 22 +- lib/aws/cloud_formation/stack.rb | 26 +-- lib/aws/cloud_formation/stack_event.rb | 8 +- lib/aws/cloud_formation/stack_output.rb | 2 +- lib/aws/cloud_formation/stack_resource.rb | 12 +- .../stack_resource_collection.rb | 10 +- lib/aws/cloud_watch/alarm.rb | 56 ++--- lib/aws/cloud_watch/alarm_collection.rb | 12 +- .../alarm_history_item_collection.rb | 4 +- lib/aws/cloud_watch/metric.rb | 4 +- .../cloud_watch/metric_alarm_collection.rb | 4 +- lib/aws/cloud_watch/metric_collection.rb | 4 +- lib/aws/cloud_watch/metric_statistics.rb | 2 +- lib/aws/core/async_handle.rb | 16 +- lib/aws/core/cacheable.rb | 26 +-- lib/aws/core/http/response.rb | 2 +- lib/aws/core/lazy_error_classes.rb | 14 +- lib/aws/core/options/json_serializer.rb | 2 +- lib/aws/core/page_result.rb | 4 +- lib/aws/core/signature/version_3.rb | 4 +- lib/aws/core/uri_escape.rb | 2 +- lib/aws/core/xml/frame_stack.rb | 2 +- lib/aws/core/xml/sax_handlers/libxml.rb | 4 +- lib/aws/core/xml/sax_handlers/nokogiri.rb | 6 +- lib/aws/core/xml/sax_handlers/rexml.rb | 10 +- lib/aws/core/xml/stub.rb | 18 +- lib/aws/dynamo_db/batch_write.rb | 22 +- lib/aws/dynamo_db/item.rb | 6 +- lib/aws/ec2.rb | 4 +- lib/aws/ec2/attachment.rb | 10 +- lib/aws/ec2/attachment_collection.rb | 2 +- lib/aws/ec2/availability_zone.rb | 4 +- lib/aws/ec2/customer_gateway.rb | 12 +- lib/aws/ec2/customer_gateway_collection.rb | 12 +- lib/aws/ec2/dhcp_options.rb | 6 +- lib/aws/ec2/dhcp_options_collection.rb | 20 +- lib/aws/ec2/elastic_ip.rb | 16 +- lib/aws/ec2/elastic_ip_collection.rb | 14 +- lib/aws/ec2/export_task.rb | 8 +- lib/aws/ec2/export_task_collection.rb | 8 +- lib/aws/ec2/image.rb | 10 +- lib/aws/ec2/instance_collection.rb | 12 +- lib/aws/ec2/internet_gateway.rb | 10 +- lib/aws/ec2/internet_gateway_collection.rb | 6 +- lib/aws/ec2/key_pair.rb | 6 +- lib/aws/ec2/key_pair_collection.rb | 22 +- lib/aws/ec2/network_acl.rb | 70 +++--- lib/aws/ec2/network_acl/association.rb | 4 +- lib/aws/ec2/network_acl/entry.rb | 44 ++-- lib/aws/ec2/network_acl_collection.rb | 4 +- lib/aws/ec2/network_interface.rb | 8 +- lib/aws/ec2/network_interface/attachment.rb | 4 +- lib/aws/ec2/network_interface_collection.rb | 10 +- lib/aws/ec2/permission_collection.rb | 2 +- lib/aws/ec2/reserved_instances_collection.rb | 4 +- .../reserved_instances_offering_collection.rb | 4 +- lib/aws/ec2/route_table.rb | 28 +-- lib/aws/ec2/route_table/association.rb | 8 +- lib/aws/ec2/route_table/route.rb | 6 +- lib/aws/ec2/route_table_collection.rb | 6 +- lib/aws/ec2/security_group_collection.rb | 26 +-- lib/aws/ec2/subnet.rb | 4 +- lib/aws/ec2/subnet_collection.rb | 16 +- lib/aws/ec2/tag_collection.rb | 2 +- lib/aws/ec2/tagged_collection.rb | 6 +- lib/aws/ec2/tagged_item.rb | 2 +- lib/aws/ec2/volume.rb | 2 +- lib/aws/ec2/volume_collection.rb | 2 +- lib/aws/ec2/vpc.rb | 12 +- lib/aws/ec2/vpc_collection.rb | 18 +- lib/aws/ec2/vpn_connection.rb | 8 +- lib/aws/ec2/vpn_connection/telemetry.rb | 2 +- lib/aws/ec2/vpn_connection_collection.rb | 10 +- lib/aws/ec2/vpn_gateway.rb | 4 +- lib/aws/ec2/vpn_gateway_collection.rb | 10 +- lib/aws/elb.rb | 2 +- lib/aws/elb/availability_zone_collection.rb | 32 +-- lib/aws/elb/errors.rb | 2 +- lib/aws/elb/load_balancer.rb | 2 +- lib/aws/elb/load_balancer_collection.rb | 4 +- lib/aws/emr/instance_group_collection.rb | 2 +- lib/aws/emr/job_flow.rb | 2 +- lib/aws/errors.rb | 8 +- lib/aws/glacier/vault_collection.rb | 2 +- lib/aws/iam.rb | 22 +- lib/aws/iam/access_key.rb | 14 +- lib/aws/iam/access_key_collection.rb | 18 +- lib/aws/iam/config.rb | 4 +- lib/aws/iam/mfa_device.rb | 2 +- lib/aws/iam/mfa_device_collection.rb | 14 +- lib/aws/iam/resource.rb | 2 +- lib/aws/iam/server_certificate_collection.rb | 2 +- lib/aws/iam/signing_certificate.rb | 16 +- lib/aws/iam/signing_certificate_collection.rb | 14 +- lib/aws/iam/user.rb | 18 +- lib/aws/iam/user_collection.rb | 10 +- lib/aws/iam/virtual_mfa_device.rb | 24 +- lib/aws/iam/virtual_mfa_device_collection.rb | 8 +- lib/aws/record.rb | 8 +- lib/aws/record/attributes.rb | 94 ++++---- lib/aws/record/conversion.rb | 2 +- lib/aws/record/dirty_tracking.rb | 56 ++--- lib/aws/record/errors.rb | 14 +- lib/aws/record/exceptions.rb | 4 +- lib/aws/record/hash_model.rb | 8 +- lib/aws/record/hash_model/finder_methods.rb | 28 +-- lib/aws/record/hash_model/scope.rb | 20 +- lib/aws/record/model.rb | 32 +-- lib/aws/record/model/attributes.rb | 46 ++-- lib/aws/record/model/finder_methods.rb | 44 ++-- lib/aws/record/model/scope.rb | 42 ++-- lib/aws/record/naming.rb | 2 +- lib/aws/record/scope.rb | 28 +-- lib/aws/record/validations.rb | 90 ++++---- lib/aws/record/validator.rb | 6 +- lib/aws/record/validators/acceptance.rb | 2 +- lib/aws/record/validators/block.rb | 2 +- lib/aws/record/validators/confirmation.rb | 2 +- lib/aws/record/validators/count.rb | 4 +- lib/aws/record/validators/exclusion.rb | 2 +- lib/aws/record/validators/format.rb | 4 +- lib/aws/record/validators/length.rb | 10 +- lib/aws/record/validators/method.rb | 2 +- lib/aws/record/validators/numericality.rb | 12 +- lib/aws/record/validators/presence.rb | 2 +- lib/aws/route_53/hosted_zone_collection.rb | 2 +- lib/aws/s3.rb | 12 +- lib/aws/s3/access_control_list.rb | 2 +- lib/aws/s3/acl_options.rb | 10 +- lib/aws/s3/bucket_collection.rb | 12 +- lib/aws/s3/client/xml.rb | 2 +- lib/aws/s3/object_metadata.rb | 2 +- lib/aws/s3/object_version.rb | 8 +- lib/aws/s3/policy.rb | 4 +- lib/aws/s3/prefixed_collection.rb | 2 +- lib/aws/s3/presigned_post.rb | 6 +- lib/aws/s3/s3_object.rb | 4 +- lib/aws/s3/tree/child_collection.rb | 6 +- lib/aws/s3/tree/leaf_node.rb | 4 +- lib/aws/s3/website_configuration.rb | 2 +- lib/aws/simple_db.rb | 30 +-- lib/aws/simple_db/attribute.rb | 14 +- lib/aws/simple_db/attribute_collection.rb | 26 +-- lib/aws/simple_db/consistent_read_option.rb | 8 +- lib/aws/simple_db/domain.rb | 4 +- lib/aws/simple_db/domain_collection.rb | 8 +- lib/aws/simple_db/domain_metadata.rb | 10 +- lib/aws/simple_db/item.rb | 8 +- lib/aws/simple_db/item_collection.rb | 36 +-- lib/aws/simple_db/put_attributes.rb | 6 +- lib/aws/simple_email_service.rb | 70 +++--- .../email_address_collection.rb | 2 +- lib/aws/simple_email_service/identity.rb | 2 +- lib/aws/simple_email_service/quotas.rb | 10 +- lib/aws/simple_workflow.rb | 26 +-- lib/aws/simple_workflow/activity_task.rb | 10 +- .../activity_task_collection.rb | 16 +- lib/aws/simple_workflow/activity_type.rb | 28 +-- .../activity_type_collection.rb | 38 ++-- lib/aws/simple_workflow/count.rb | 10 +- lib/aws/simple_workflow/decision_task.rb | 206 ++++++++--------- .../decision_task_collection.rb | 44 ++-- lib/aws/simple_workflow/domain.rb | 18 +- lib/aws/simple_workflow/domain_collection.rb | 24 +- lib/aws/simple_workflow/history_event.rb | 22 +- .../history_event_collection.rb | 6 +- lib/aws/simple_workflow/option_formatters.rb | 10 +- lib/aws/simple_workflow/request.rb | 2 +- lib/aws/simple_workflow/resource.rb | 2 +- lib/aws/simple_workflow/type.rb | 16 +- lib/aws/simple_workflow/type_collection.rb | 18 +- lib/aws/simple_workflow/workflow_execution.rb | 102 ++++----- .../workflow_execution_collection.rb | 132 +++++------ lib/aws/simple_workflow/workflow_type.rb | 30 +-- .../workflow_type_collection.rb | 32 +-- lib/aws/sns.rb | 6 +- lib/aws/sns/policy.rb | 2 +- lib/aws/sns/subscription.rb | 4 +- lib/aws/sns/topic.rb | 58 ++--- lib/aws/sns/topic_collection.rb | 2 +- lib/aws/sqs/queue.rb | 48 ++-- lib/aws/sqs/queue_collection.rb | 14 +- lib/aws/sqs/received_message.rb | 4 +- lib/aws/sts/client.rb | 2 +- lib/net/http/connection_pool/connection.rb | 8 +- .../auto_scaling/activity_collection_spec.rb | 2 +- .../aws/auto_scaling/group_collection_spec.rb | 10 +- spec/aws/auto_scaling/group_spec.rb | 72 +++--- .../auto_scaling/instance_collection_spec.rb | 12 +- spec/aws/auto_scaling/instance_spec.rb | 4 +- .../launch_configuration_collection_spec.rb | 10 +- .../auto_scaling/launch_configuration_spec.rb | 2 +- ...ification_configuration_collection_spec.rb | 6 +- .../notification_configuration_spec.rb | 8 +- spec/aws/auto_scaling/request_spec.rb | 4 +- .../scaling_policy_colletion_spec.rb | 10 +- spec/aws/auto_scaling/scaling_policy_spec.rb | 6 +- .../scheduled_action_collection_spec.rb | 6 +- .../aws/auto_scaling/scheduled_action_spec.rb | 2 +- spec/aws/cloud_formation/request_spec.rb | 4 +- spec/aws/cloud_formation/stack_spec.rb | 4 +- .../stack_summary_collection_spec.rb | 6 +- spec/aws/cloud_formation_spec.rb | 14 +- spec/aws/cloud_watch/alarm_collection_spec.rb | 18 +- .../alarm_history_item_collection_spec.rb | 2 +- .../cloud_watch/alarm_history_item_spec.rb | 6 +- .../metric_alarm_collection_spec.rb | 2 +- spec/aws/config_spec.rb | 20 +- spec/aws/core/cacheable_spec.rb | 6 +- spec/aws/core/collection/simple_spec.rb | 30 +-- .../core/collection/with_next_token_spec.rb | 2 +- spec/aws/core/configuration_spec.rb | 18 +- spec/aws/core/credential_providers_spec.rb | 16 +- spec/aws/core/http/request_param_spec.rb | 10 +- spec/aws/core/http/request_spec.rb | 14 +- spec/aws/core/http/response_spec.rb | 4 +- spec/aws/core/indifferent_hash_spec.rb | 10 +- spec/aws/core/log_formatter_spec.rb | 10 +- spec/aws/core/policy_spec.rb | 4 +- spec/aws/dynamo_db/batch_write_spec.rb | 10 +- spec/aws/dynamo_db/request_spec.rb | 4 +- spec/aws/dynamo_db/table_spec.rb | 6 +- spec/aws/ec2/attachment_spec.rb | 6 +- spec/aws/ec2/client/xml_spec.rb | 2 +- spec/aws/ec2/client_spec.rb | 4 +- .../ec2/customer_gateway_collection_spec.rb | 2 +- spec/aws/ec2/customer_gateway_spec.rb | 8 +- spec/aws/ec2/dhcp_options_collection_spec.rb | 2 +- spec/aws/ec2/dhcp_options_spec.rb | 24 +- spec/aws/ec2/elastic_ip_spec.rb | 24 +- spec/aws/ec2/export_task_spec.rb | 6 +- spec/aws/ec2/image_spec.rb | 4 +- spec/aws/ec2/instance_collection_spec.rb | 16 +- .../ec2/internet_gateway_collection_spec.rb | 2 +- spec/aws/ec2/internet_gateway_spec.rb | 2 +- spec/aws/ec2/key_pair_spec.rb | 4 +- spec/aws/ec2/network_acl_collection_spec.rb | 2 +- spec/aws/ec2/network_acl_spec.rb | 2 +- spec/aws/ec2/network_interface_spec.rb | 38 ++-- spec/aws/ec2/request_spec.rb | 2 +- .../ec2/reserved_instances_offering_spec.rb | 2 +- spec/aws/ec2/route_table_collection_spec.rb | 2 +- spec/aws/ec2/route_table_spec.rb | 10 +- .../egress_ip_permission_collection_spec.rb | 6 +- .../ip_permission_collection_spec.rb | 2 +- .../ec2/security_group/ip_permission_spec.rb | 4 +- spec/aws/ec2/security_group_spec.rb | 46 ++-- spec/aws/ec2/subnet_collection_spec.rb | 4 +- spec/aws/ec2/subnet_spec.rb | 26 +-- spec/aws/ec2/tag_collection_spec.rb | 2 +- spec/aws/ec2/tag_spec.rb | 6 +- spec/aws/ec2/vpc_spec.rb | 2 +- .../aws/ec2/vpn_connection_collection_spec.rb | 8 +- spec/aws/ec2/vpn_connection_spec.rb | 8 +- spec/aws/ec2/vpn_gateway_spec.rb | 2 +- .../elb/availability_zone_collection_spec.rb | 2 +- .../backend_server_policy_collection_spec.rb | 6 +- spec/aws/elb/instance_collection_spec.rb | 14 +- spec/aws/elb/listener_collection_spec.rb | 2 +- .../load_balancer_policy_collection_spec.rb | 4 +- spec/aws/elb/load_balancer_policy_spec.rb | 6 +- spec/aws/elb/load_balancer_spec.rb | 12 +- spec/aws/elb_spec.rb | 2 +- spec/aws/errors_spec.rb | 10 +- spec/aws/iam/access_key_collection_spec.rb | 24 +- spec/aws/iam/access_key_spec.rb | 28 +-- spec/aws/iam/mfa_device_collection_spec.rb | 4 +- spec/aws/iam/mfa_device_spec.rb | 4 +- spec/aws/iam/request_spec.rb | 4 +- .../iam/server_certificate_collection_spec.rb | 2 +- spec/aws/iam/server_certificate_spec.rb | 2 +- .../signing_certificate_collection_spec.rb | 26 +-- spec/aws/iam/signing_certificate_spec.rb | 30 +-- spec/aws/iam/user_policy_spec.rb | 10 +- spec/aws/iam/user_spec.rb | 12 +- .../iam/virtual_mfa_device_collection_spec.rb | 4 +- spec/aws/iam/virtual_mfa_device_spec.rb | 8 +- spec/aws/rails_spec.rb | 54 ++--- spec/aws/record/attributes_spec.rb | 64 +++--- .../record/hash_model/class_methods_spec.rb | 10 +- .../record/model/attribute_deletion_spec.rb | 6 +- spec/aws/record/model/class_methods_spec.rb | 74 +++--- spec/aws/record/model/dirty_tracking_spec.rb | 150 ++++++------- spec/aws/record/model/empty_records_spec.rb | 6 +- spec/aws/record/model/errors_spec.rb | 10 +- .../aws/record/model/instance_methods_spec.rb | 18 +- .../record/model/optimistic_locking_spec.rb | 32 +-- spec/aws/record/model/scope_spec.rb | 212 +++++++++--------- spec/aws/record/model/timestamps_spec.rb | 8 +- .../aws/record/validations/acceptance_spec.rb | 2 +- .../record/validations/confirmation_spec.rb | 2 +- spec/aws/record/validations/count_spec.rb | 12 +- spec/aws/record/validations/exclusion_spec.rb | 2 +- spec/aws/record/validations/format_spec.rb | 30 +-- spec/aws/record/validations/inclusion_spec.rb | 2 +- spec/aws/record/validations/length_spec.rb | 24 +- .../record/validations/numericality_spec.rb | 110 ++++----- spec/aws/route_53/client_spec.rb | 14 +- spec/aws/s3/bucket_collection_spec.rb | 8 +- spec/aws/s3/bucket_version_collection_spec.rb | 8 +- spec/aws/s3/client/xml_spec.rb | 28 +-- spec/aws/s3/object_collection_spec.rb | 28 +-- spec/aws/s3/object_metadata_spec.rb | 2 +- spec/aws/s3/object_version_collection_spec.rb | 6 +- spec/aws/s3/object_version_spec.rb | 4 +- spec/aws/s3/policy_spec.rb | 4 +- spec/aws/s3/request_spec.rb | 10 +- spec/aws/s3_spec.rb | 2 +- .../simple_db/attribute_collection_spec.rb | 18 +- spec/aws/simple_db/attribute_spec.rb | 14 +- spec/aws/simple_db/client_spec.rb | 22 +- spec/aws/simple_db/domain_collection_spec.rb | 18 +- spec/aws/simple_db/domain_metadata_spec.rb | 2 +- spec/aws/simple_db/domain_spec.rb | 18 +- spec/aws/simple_db/errors_spec.rb | 4 +- spec/aws/simple_db/item_collection_spec.rb | 38 ++-- spec/aws/simple_db/item_data_spec.rb | 2 +- spec/aws/simple_db/item_spec.rb | 8 +- spec/aws/simple_db/request_spec.rb | 2 +- spec/aws/simple_db_spec.rb | 4 +- .../email_address_collection_spec.rb | 10 +- .../identity_collection_spec.rb | 6 +- spec/aws/simple_email_service/quotas_spec.rb | 10 +- spec/aws/simple_email_service/request_spec.rb | 4 +- spec/aws/simple_email_service_spec.rb | 20 +- .../activity_task_collection_spec.rb | 12 +- .../aws/simple_workflow/activity_task_spec.rb | 6 +- .../activity_type_collection_spec.rb | 16 +- .../aws/simple_workflow/activity_type_spec.rb | 2 +- spec/aws/simple_workflow/count_spec.rb | 4 +- .../decision_task_collection_spec.rb | 50 ++--- .../aws/simple_workflow/decision_task_spec.rb | 62 ++--- .../simple_workflow/domain_collection_spec.rb | 12 +- spec/aws/simple_workflow/domain_spec.rb | 6 +- .../history_event_collection_spec.rb | 4 +- .../aws/simple_workflow/history_event_spec.rb | 6 +- .../workflow_execution_collection_spec.rb | 24 +- .../workflow_execution_spec.rb | 54 ++--- .../workflow_type_collection_spec.rb | 8 +- .../aws/simple_workflow/workflow_type_spec.rb | 10 +- spec/aws/simple_workflow_spec.rb | 2 +- spec/aws/sns/subscription_spec.rb | 2 +- spec/aws/sns/topic_spec.rb | 44 ++-- spec/aws/sns_spec.rb | 2 +- spec/aws/sqs/queue_collection_spec.rb | 2 +- spec/aws/sqs/queue_spec.rb | 20 +- spec/aws/sqs/received_message_spec.rb | 2 +- spec/aws/sqs/received_sns_message_spec.rb | 6 +- spec/aws/sts/client_spec.rb | 2 +- spec/aws_spec.rb | 2 +- spec/dummy_server.rb | 6 +- spec/mock_ec2_metadata_server.rb | 6 +- .../http/connection_pool/connection_spec.rb | 12 +- .../http/connection_pool/live_tests_spec.rb | 2 +- spec/net/http/connection_pool_spec.rb | 8 +- spec/shared/aws_record_examples.rb | 4 +- .../ec2/describe_call_attribute_examples.rb | 2 +- .../ec2/fitlered_collection_examples.rb | 2 +- spec/shared/ec2/model_object_examples.rb | 2 +- spec/shared/ec2/tagged_collection_examples.rb | 28 +-- spec/shared/ec2/tagged_item_examples.rb | 2 +- spec/shared/policy_examples.rb | 184 +++++++-------- spec/shared/record/abstract_base_examples.rb | 72 +++--- .../record/optimistic_lockable_examples.rb | 12 +- spec/shared/record/shardable_examples.rb | 8 +- spec/shared/record/validation_examples.rb | 46 ++-- spec/shared/s3/model_object_examples.rb | 4 +- spec/shared/signature/version_4_examples.rb | 4 +- ...accepts_consistent_read_option_examples.rb | 8 +- .../shared/simple_db/model_object_examples.rb | 2 +- .../type_collection_examples.rb | 10 +- spec/shared/simple_workflow/type_examples.rb | 6 +- .../sns/has_delivery_policy_examples.rb | 20 +- spec/shared/sqs/model_object_examples.rb | 4 +- 452 files changed, 3080 insertions(+), 3080 deletions(-) diff --git a/features/auto_scaling/launch_configurations.feature b/features/auto_scaling/launch_configurations.feature index d2df2a7ac5c..ddd9e2858d8 100644 --- a/features/auto_scaling/launch_configurations.feature +++ b/features/auto_scaling/launch_configurations.feature @@ -31,4 +31,4 @@ Feature: Auto Scaling Launch Configurations And I create a launch configuration with options When I get the launch configuraiton by name Then the launch configuraiton should have the same attributes - + diff --git a/features/auto_scaling/step_definitions/auto_scaling.rb b/features/auto_scaling/step_definitions/auto_scaling.rb index e2b763b5346..506d80dfc1c 100644 --- a/features/auto_scaling/step_definitions/auto_scaling.rb +++ b/features/auto_scaling/step_definitions/auto_scaling.rb @@ -35,7 +35,7 @@ begin launch_config.delete rescue AWS::AutoScaling::Errors::ValidationError - # throws a name validation error when it cant find a launch + # throws a name validation error when it cant find a launch # configuration with the given name -- already deleted end end diff --git a/features/auto_scaling/step_definitions/notification_configurations.rb b/features/auto_scaling/step_definitions/notification_configurations.rb index 2d26be763b3..9b6e5989951 100644 --- a/features/auto_scaling/step_definitions/notification_configurations.rb +++ b/features/auto_scaling/step_definitions/notification_configurations.rb @@ -20,7 +20,7 @@ end Then /^the auto scaling group should have the notification configuration$/ do - @auto_scaling_group.notification_configurations.should + @auto_scaling_group.notification_configurations.should include(@notification_configuration) end diff --git a/features/auto_scaling/step_definitions/scaling_policies.rb b/features/auto_scaling/step_definitions/scaling_policies.rb index a9c62c8ec99..dd82d02ed39 100644 --- a/features/auto_scaling/step_definitions/scaling_policies.rb +++ b/features/auto_scaling/step_definitions/scaling_policies.rb @@ -13,7 +13,7 @@ When /^I create an auto scaling policy$/ do @policy_name = "ruby-integration-test-#{Time.now.to_i}" - @scaling_policy = @auto_scaling_group.scaling_policies.create(@policy_name, + @scaling_policy = @auto_scaling_group.scaling_policies.create(@policy_name, :adjustment_type => 'ExactCapacity', :scaling_adjustment => 1) end @@ -46,7 +46,7 @@ options[hash['OPT'].to_sym] = value end @policy_name = "ruby-integration-test-#{Time.now.to_i}" - @scaling_policy = @auto_scaling_group.scaling_policies.create(@policy_name, + @scaling_policy = @auto_scaling_group.scaling_policies.create(@policy_name, options) end diff --git a/features/cloud_formation/step_definitions/stacks.rb b/features/cloud_formation/step_definitions/stacks.rb index 1822c6375b9..5c296fd3882 100644 --- a/features/cloud_formation/step_definitions/stacks.rb +++ b/features/cloud_formation/step_definitions/stacks.rb @@ -73,7 +73,7 @@ def simple_template ami, description end Then /^I should be able to locate the stack in the summarized stacks$/ do - @cloud_formation.stack_summaries.map{|s| s[:stack_name] }.should + @cloud_formation.stack_summaries.map{|s| s[:stack_name] }.should include(@stack.name) end @@ -119,7 +119,7 @@ def simple_template ami, description 'SecurityGroup' => @security_group.name, 'InstanceType' => 'm1.large', } - @stack = @cloud_formation.stacks.create(@stack_name, @template, + @stack = @cloud_formation.stacks.create(@stack_name, @template, :parameters => @stack_params) @created_stacks << @stack end diff --git a/features/cloud_formation/templates.feature b/features/cloud_formation/templates.feature index 955b4926e90..4813c80fca1 100644 --- a/features/cloud_formation/templates.feature +++ b/features/cloud_formation/templates.feature @@ -33,7 +33,7 @@ Feature: CloudFormations templates """ Then I should get a response like: """ - {:capabilities=>[], :description=>"A simple template", :parameters=>[]} + {:capabilities=>[], :description=>"A simple template", :parameters=>[]} """ Scenario: Validating an invalid template @@ -49,7 +49,7 @@ Feature: CloudFormations templates "Type" : "String" } }, - + "Resources": { "web": { "Type": "AWS::EC2::Instance", @@ -70,5 +70,5 @@ Feature: CloudFormations templates """ Then I should get a response like: """ - {:code=>"ValidationError", :message=>"Invalid template resource property 'ConfigurationTemplates'"} + {:code=>"ValidationError", :message=>"Invalid template resource property 'ConfigurationTemplates'"} """ diff --git a/features/cloud_front/step_definitions/client.rb b/features/cloud_front/step_definitions/client.rb index a8522ce0cd2..85dec82b4a4 100644 --- a/features/cloud_front/step_definitions/client.rb +++ b/features/cloud_front/step_definitions/client.rb @@ -38,7 +38,7 @@ :caller_reference => '123', :aliases => { :quantity => 0, - }, + }, :default_root_object => '/', :origins => { :quantity => 0, diff --git a/features/cloud_watch/step_definitions/metrics.rb b/features/cloud_watch/step_definitions/metrics.rb index a64f6881a2c..b858435043f 100644 --- a/features/cloud_watch/step_definitions/metrics.rb +++ b/features/cloud_watch/step_definitions/metrics.rb @@ -28,7 +28,7 @@ end Then /^I should be able to get statistics for the metric$/ do - eventually do + eventually do now = Time.now @statistics = @metric.statistics({ :start_time => now - 3600, diff --git a/features/dynamo_db/step_definitions/tables.rb b/features/dynamo_db/step_definitions/tables.rb index 00842acb070..bffc8ffef6a 100644 --- a/features/dynamo_db/step_definitions/tables.rb +++ b/features/dynamo_db/step_definitions/tables.rb @@ -91,7 +91,7 @@ When /^I update the provisioning throughput to (\d+) rcu and (\d+) wcu$/ do |rcu, wcu| @table.provision_throughput( - :read_capacity_units => rcu.to_i, + :read_capacity_units => rcu.to_i, :write_capacity_units => wcu.to_i) end diff --git a/features/ec2/customer_gateways.feature b/features/ec2/customer_gateways.feature index 5935e89f8f7..c603ef3bbd8 100644 --- a/features/ec2/customer_gateways.feature +++ b/features/ec2/customer_gateways.feature @@ -15,7 +15,7 @@ Feature: EC2 VPC Customer Gateways Scenario: Create a customer gateway - When I create a customer gateway + When I create a customer gateway Then the customer gateway should exist And the customer gateway should eventually have the state "available" @@ -26,10 +26,10 @@ Feature: EC2 VPC Customer Gateways And the customer gateway should have the ip address of "1.2.3.4" Scenario: Delete an internet gateway - Given I create a customer gateway + Given I create a customer gateway When I delete the customer gateway And the customer gateway should eventually have the state "deleted" Scenario: Enumerating Gateways - Given I create a customer gateway + Given I create a customer gateway Then the account customer gatways should include the gateway diff --git a/features/ec2/images.feature b/features/ec2/images.feature index 4c4505a5344..2d386dfb508 100644 --- a/features/ec2/images.feature +++ b/features/ec2/images.feature @@ -72,7 +72,7 @@ Feature: Basic Image Operations | param | Action | DescribeImages | | param | Owner.1 | self | - # ari-fe916297 is a public ami by IBM, if this ami is removed then + # ari-fe916297 is a public ami by IBM, if this ami is removed then # the test will need to be updated Scenario: Getting product codes When I ask for the image "ari-fe916297" by ID diff --git a/features/ec2/internet_gateways.feature b/features/ec2/internet_gateways.feature index d7ce7fb9aae..3b6ecdc375d 100644 --- a/features/ec2/internet_gateways.feature +++ b/features/ec2/internet_gateways.feature @@ -16,14 +16,14 @@ Feature: EC2 VPC Internet Gateways Scenario: Create an internet gateway - When I create an internet gateway + When I create an internet gateway Then the internet gateway should exist And a request should have been made like: | TYPE | NAME | VALUE | | param | Action | CreateInternetGateway | Scenario: Delete an internet gateway - Given I create an internet gateway + Given I create an internet gateway When I delete the internet gateway Then the internet gateway should not exist And a request should have been made like: diff --git a/features/ec2/route_tables.feature b/features/ec2/route_tables.feature index d84b325cf81..6f44bb67069 100644 --- a/features/ec2/route_tables.feature +++ b/features/ec2/route_tables.feature @@ -17,7 +17,7 @@ Feature: EC2 Route Tables Scenario: Creating a Route Table Given I create a vpc - When I create a route table + When I create a route table Then a request should have been made like: | TYPE | NAME | VALUE | | param | Action | CreateRouteTable | @@ -25,7 +25,7 @@ Feature: EC2 Route Tables Scenario: Deleting a Route Table Given I create a vpc - And I create a route table + And I create a route table When I delete the route table Then a request should have been made like: | TYPE | NAME | VALUE | @@ -39,7 +39,7 @@ Feature: EC2 Route Tables Scenario: Associating subnets to route tables Given I create a vpc - And I create a route table + And I create a route table And I create a subnet When I assign the route table to the subnet Then the route table should be associated with the subnet @@ -56,12 +56,12 @@ Feature: EC2 Route Tables When I request to run an vpc instance with the following parameters: | parameter | value | | image_id | ami-8c1fece5 | - And I create a route table + And I create a route table When I create a route for the vpc instance Then the route table should have 2 routes And a request should have been made like: | TYPE | NAME | VALUE | | param | Action | CreateRoute | | param_match | RouteTableId | rtb-.+ | - + diff --git a/features/ec2/security_groups.feature b/features/ec2/security_groups.feature index d6970d4e0e1..bece9099dd2 100644 --- a/features/ec2/security_groups.feature +++ b/features/ec2/security_groups.feature @@ -24,7 +24,7 @@ Feature: EC2 Security Groups Then The security group should be in the list Scenario: Delete a security group - Given I create a security group + Given I create a security group When I delete the security group Then The security group should not be in the list diff --git a/features/ec2/step_definitions/ec2.rb b/features/ec2/step_definitions/ec2.rb index f48cf012a2f..7fa1bf6bffb 100644 --- a/features/ec2/step_definitions/ec2.rb +++ b/features/ec2/step_definitions/ec2.rb @@ -53,12 +53,12 @@ end @created_vpn_gateways.each do |gateway| - gateway.delete + gateway.delete end @created_customer_gateways.each do |gateway| begin - gateway.delete + gateway.delete rescue AWS::EC2::Errors::InvalidCustomerGatewayID::NotFound # already deleted end @@ -154,7 +154,7 @@ sleep(1) until @started_instances.all?{|i| i.status == :terminated } # even after all isntances indicate they are terminated, # it can take some time before the subnet may be deleted - sleep(10) + sleep(10) end @created_subnets.each do |subnet| diff --git a/features/ec2/step_definitions/network_interfaces.rb b/features/ec2/step_definitions/network_interfaces.rb index 71efc8ae6b9..b8a2f564bd6 100644 --- a/features/ec2/step_definitions/network_interfaces.rb +++ b/features/ec2/step_definitions/network_interfaces.rb @@ -28,7 +28,7 @@ end Then /^the network interface should have the security groups$/ do - @network_interface.security_groups.sort_by(&:name).should == + @network_interface.security_groups.sort_by(&:name).should == @security_groups.sort_by(&:name) end @@ -65,7 +65,7 @@ end Then /^the network interface should have the security groups assigned$/ do - @network_interface.security_groups.sort_by(&:id).should == + @network_interface.security_groups.sort_by(&:id).should == @security_groups.sort_by(&:id) end diff --git a/features/ec2/step_definitions/route_tables.rb b/features/ec2/step_definitions/route_tables.rb index a6df2437a25..faad0cdbc3c 100644 --- a/features/ec2/step_definitions/route_tables.rb +++ b/features/ec2/step_definitions/route_tables.rb @@ -25,7 +25,7 @@ end When /^I assign the route table to the subnet$/ do - @subnet.route_table = @route_table + @subnet.route_table = @route_table end Then /^the route table should be associated with the subnet$/ do diff --git a/features/ec2/step_definitions/security_groups.rb b/features/ec2/step_definitions/security_groups.rb index 7140711281b..1addcc3d439 100644 --- a/features/ec2/step_definitions/security_groups.rb +++ b/features/ec2/step_definitions/security_groups.rb @@ -102,7 +102,7 @@ def security_groups_from_table table, owner_id end When /^I authorize "([^\"]*)" over port (\d+) for:$/ do |protocol, port, table| - ip_ranges = ip_ranges_from_table(table) + ip_ranges = ip_ranges_from_table(table) groups = security_groups_from_table(table, @security_group.owner_id) @security_group.authorize_ingress(protocol, port, *(ip_ranges + groups)) end @@ -110,7 +110,7 @@ def security_groups_from_table table, owner_id Then /^The security group should allow "([^\"]*)" over port (\d+) for:$/ do |protocol, port, table| permissions = @security_group.ip_permissions - ip_ranges = ip_ranges_from_table(table) + ip_ranges = ip_ranges_from_table(table) groups = security_groups_from_table(table, @security_group.owner_id) permissions.any? do |p| p.protocol.to_s == protocol and diff --git a/features/ec2/step_definitions/snapshot_attributes.rb b/features/ec2/step_definitions/snapshot_attributes.rb index 43836e67112..f0715d78137 100644 --- a/features/ec2/step_definitions/snapshot_attributes.rb +++ b/features/ec2/step_definitions/snapshot_attributes.rb @@ -44,7 +44,7 @@ step %(I create a volume) - # it can take a while before the volume is in a state where snapshots + # it can take a while before the volume is in a state where snapshots # are allowed eventually do @snapshot = @volume.create_snapshot(description) diff --git a/features/ec2/subnets.feature b/features/ec2/subnets.feature index a33e3ddf669..956ab245296 100644 --- a/features/ec2/subnets.feature +++ b/features/ec2/subnets.feature @@ -42,4 +42,4 @@ Feature: EC2 VPC | TYPE | NAME | VALUE | | param | Action | DescribeSubnets | | param_match | SubnetId.1 | subnet-.+ | - + diff --git a/features/ec2/vpn_gateways.feature b/features/ec2/vpn_gateways.feature index be03be16971..dce507166f0 100644 --- a/features/ec2/vpn_gateways.feature +++ b/features/ec2/vpn_gateways.feature @@ -16,12 +16,12 @@ Feature: EC2 VPC VPN Gateways Scenario: Create a vpn gateway - When I create a vpn gateway + When I create a vpn gateway Then the vpn gateway should exist And the vpn gateway state should eventually be "available" Scenario: Delete a vpn gateway - Given I create a vpn gateway + Given I create a vpn gateway When I delete the vpn gateway Then the vpn gateway state should eventually be "deleted" diff --git a/features/elb/backend_server_policies.feature b/features/elb/backend_server_policies.feature index ef352652d47..6ade93989be 100644 --- a/features/elb/backend_server_policies.feature +++ b/features/elb/backend_server_policies.feature @@ -48,7 +48,7 @@ o50MymfqtoVcebZcXbiDVAXW1cPEHKLBXecX6/LZ+GOzEsUOxgt7Xs9uabqp | param | Action | SetLoadBalancerPoliciesForBackendServer | | param | InstancePort | 80 | | param | PolicyNames.member.1 | bsap | - + @adding Scenario: Adding backend server policies Given I create a load balancer diff --git a/features/elb/listeners.feature b/features/elb/listeners.feature index 313237f3ab5..b122e97da1d 100644 --- a/features/elb/listeners.feature +++ b/features/elb/listeners.feature @@ -36,7 +36,7 @@ Feature: ELB Listeners | param | Listeners.member.1.InstanceProtocol | HTTP | | param_match | LoadBalancerName | ruby-test-.* | - Scenario: Get a listener + Scenario: Get a listener Given I create a load balancer And I create a load balancer listener with the following options: | OPT_NAME | VALUE | CAST | @@ -51,7 +51,7 @@ Feature: ELB Listeners And the listener should have the instance protocol :http When I delete the listener Then the listener should not exist - + Scenario: Find a listener Given I create a load balancer @@ -83,4 +83,4 @@ Feature: ELB Listeners When I upload an IAM server certificate And I set the server certificate on the load balancer listener Then the load balancer listener should have the new certificate - + diff --git a/features/elb/load_balancers.feature b/features/elb/load_balancers.feature index 2f68cb936d6..91989d0ba16 100644 --- a/features/elb/load_balancers.feature +++ b/features/elb/load_balancers.feature @@ -32,7 +32,7 @@ Feature: ELB Load Balancers | param | Listeners.member.1.InstancePort | 80 | | param | Listeners.member.1.InstanceProtocol | HTTP | | param | Listeners.member.1.LoadBalancerPort | 80 | - + @delete Scenario: Delete a load balancer Given I create a load balancer @@ -57,11 +57,11 @@ Feature: ELB Load Balancers Given I create a load balancer When I update the health check configuration with: | OPT_NAME | OPT_VALUE | - | interval | 12 | + | interval | 12 | | timeout | 11 | Then the health check configuration should have the following values: | OPT_NAME | OPT_VALUE | - | interval | 12 | + | interval | 12 | | timeout | 11 | @ec2 @security_groups @@ -77,4 +77,4 @@ Feature: ELB Load Balancers | param | IpPermissions.1.Groups.1.UserId | amazon-elb | | param | IpPermissions.1.Groups.1.GroupName | amazon-elb-sg | | param | IpPermissions.1.IpProtocol | tcp | - + diff --git a/features/elb/step_definitions/backend_server_policies.rb b/features/elb/step_definitions/backend_server_policies.rb index c2cb5990c43..c3778320544 100644 --- a/features/elb/step_definitions/backend_server_policies.rb +++ b/features/elb/step_definitions/backend_server_policies.rb @@ -49,10 +49,10 @@ KEY public_key_policy = @load_balancer.policies.create("pkp-for-#{name}", - 'PublicKeyPolicyType', 'PublicKey' => public_key.strip) + 'PublicKeyPolicyType', 'PublicKey' => public_key.strip) - @backend_policy = @load_balancer.policies.create(name, - 'BackendServerAuthenticationPolicyType', + @backend_policy = @load_balancer.policies.create(name, + 'BackendServerAuthenticationPolicyType', 'PublicKeyPolicyName' => public_key_policy.name) end @@ -70,7 +70,7 @@ end Then /^the load balancer should have the following backend server policies on port (\d+):$/ do |arg1, table| - + policy_names = [] table.hashes.each do |hash| policy_names << hash["NAME"] diff --git a/features/elb/step_definitions/instances.rb b/features/elb/step_definitions/instances.rb index 5649900b9b8..9c135b92e2f 100644 --- a/features/elb/step_definitions/instances.rb +++ b/features/elb/step_definitions/instances.rb @@ -29,6 +29,6 @@ Then /^instance health should return hash$/ do health = @load_balancer.instances[@instance.id].elb_health - health.keys.collect(&:to_s).sort.should == + health.keys.collect(&:to_s).sort.should == [:description, :reason_code, :state].collect(&:to_s).sort end diff --git a/features/elb/step_definitions/listeners.rb b/features/elb/step_definitions/listeners.rb index be810ee159b..6db483335c9 100644 --- a/features/elb/step_definitions/listeners.rb +++ b/features/elb/step_definitions/listeners.rb @@ -43,7 +43,7 @@ Given /^I create a load balancer listener with the server certificate$/ do sleep(10) - # eventual consistency, it takes a while before + # eventual consistency, it takes a while before # elb acknowledges the iam server cert @listener = @load_balancer.listeners.create( :port => 443, @@ -54,8 +54,8 @@ end When /^I set the server certificate on the load balancer listener$/ do - sleep(10) - # eventual consistency, it takes a while before + sleep(10) + # eventual consistency, it takes a while before # elb acknowledges the iam server cert @listener.server_certificate = @server_cert.arn end @@ -80,7 +80,7 @@ Then /^the listener on port (\d+) should have no policy$/ do |port| @load_balancer.listeners[port].policy.should == nil end - + Then /^the listener should exist$/ do @listener.exists?.should == true end diff --git a/features/elb/step_definitions/load_balancers.rb b/features/elb/step_definitions/load_balancers.rb index 48d85003c3f..3b4599c0039 100644 --- a/features/elb/step_definitions/load_balancers.rb +++ b/features/elb/step_definitions/load_balancers.rb @@ -14,7 +14,7 @@ When /^I create a load balancer$/ do name = "ruby-test-#{Time.now.to_i}-#{rand(1000)}" - + @load_balancer = @elb.load_balancers.create(name, :availability_zones => %w(us-east-1a us-east-1b), :listeners => { @@ -36,7 +36,7 @@ table.hashes.each do |hash| zones << hash["AZ_NAME"] end - + @load_balancer = @elb.load_balancers.create(name, :availability_zones => zones, :listeners => { diff --git a/features/elb/step_definitions/policies.rb b/features/elb/step_definitions/policies.rb index bf27363a3ee..c39d573d63a 100644 --- a/features/elb/step_definitions/policies.rb +++ b/features/elb/step_definitions/policies.rb @@ -20,7 +20,7 @@ attributes[name] << hash['VALUE'] end - @load_balancer_policy = @load_balancer.policies.create(policy_name, + @load_balancer_policy = @load_balancer.policies.create(policy_name, policy_type, attributes) end diff --git a/features/iam/account_aliases.feature b/features/iam/account_aliases.feature index b4c7827df6c..250c420f085 100644 --- a/features/iam/account_aliases.feature +++ b/features/iam/account_aliases.feature @@ -53,7 +53,7 @@ Feature: IAM Account Aliases | TYPE | NAME | VALUE | | param | Action | CreateAccountAlias | | param_match | AccountAlias | rubyintegrationtest\d+ | - + @collection @delete Scenario: Delete an account alias @@ -65,7 +65,7 @@ Feature: IAM Account Aliases | param_match | AccountAlias | rubyintegrationtest\d+ | @collection @list - Scenario: Paging account aliases + Scenario: Paging account aliases Given I create 4 account aliases When I list account aliases with a limit of 4 and batch_size of 2 Then 1 request should have been made like: diff --git a/features/iam/signing_certificates.feature b/features/iam/signing_certificates.feature index 25817d4a88a..53e82655e46 100644 --- a/features/iam/signing_certificates.feature +++ b/features/iam/signing_certificates.feature @@ -31,7 +31,7 @@ Feature: IAM Signing Certificates When I get a reference to the signing certificate Then the signing certificate contents should match And the signing certificate status should be "active" - And the signing certificate user be nil + And the signing certificate user be nil And 2 requests should have been made like: | TYPE | NAME | VALUE | | param | Action | ListSigningCertificates | @@ -43,7 +43,7 @@ Feature: IAM Signing Certificates When I get a reference to the signing certificate Then the signing certificate contents should match And the signing certificate status should be "active" - And the signing certificate user be nil + And the signing certificate user be nil And 0 request should have been made like: | TYPE | NAME | VALUE | | param | Action | ListSigningCertificates | diff --git a/features/iam/step_definitions/iam.rb b/features/iam/step_definitions/iam.rb index ef4831f4dd6..e2b3ca64fbe 100644 --- a/features/iam/step_definitions/iam.rb +++ b/features/iam/step_definitions/iam.rb @@ -70,7 +70,7 @@ # some tests delete the aliases they create end end - + @created_user_policies.each do |user_policy| user_policy.delete end diff --git a/features/iam/step_definitions/signing_certificates.rb b/features/iam/step_definitions/signing_certificates.rb index 2cc3703d41d..3431102cc89 100644 --- a/features/iam/step_definitions/signing_certificates.rb +++ b/features/iam/step_definitions/signing_certificates.rb @@ -16,21 +16,21 @@ Given /^I create an X\.509 cert$/ do @cert = OpenSSL::X509::Certificate.new @cert.not_before = Time.now - 86400 - @cert.not_after = Time.now + 86400 - @cert.issuer = OpenSSL::X509::Name.parse('CN=sample') + @cert.not_after = Time.now + 86400 + @cert.issuer = OpenSSL::X509::Name.parse('CN=sample') @cert.version = 2 # X.509v3 - @cert.public_key = OpenSSL::PKey::RSA.generate(1024) + @cert.public_key = OpenSSL::PKey::RSA.generate(1024) @cert.serial = 1 end When /^I upload a signing certificate$/ do - step "I create an X.509 cert" + step "I create an X.509 cert" @signing_certificate = @iam.signing_certificates.upload(@cert.to_pem) @uploaded_signing_certificates << @signing_certificate end When /^I upload a signing certificate for the user$/ do - step "I create an X.509 cert" + step "I create an X.509 cert" @signing_certificate = @user.signing_certificates.upload(@cert.to_pem) @uploaded_signing_certificates << @signing_certificate end diff --git a/features/iam/users.feature b/features/iam/users.feature index 3b034acb4d0..adeb43183b6 100644 --- a/features/iam/users.feature +++ b/features/iam/users.feature @@ -35,7 +35,7 @@ Feature: IAM Users | TYPE | NAME | VALUE | | param | Action | DeleteUser | | param_match | UserName | ruby-integreation-test-\d+ | - + @get Scenario: Getting user details Given I create an IAM user diff --git a/features/record/count.feature b/features/record/count.feature index 04b9c65c6e6..e08277f0df4 100644 --- a/features/record/count.feature +++ b/features/record/count.feature @@ -16,7 +16,7 @@ Feature: Scoped counts As an ORM user - I want to use an expressive interface + I want to use an expressive interface So that I can count records. @query @where diff --git a/features/record/find.feature b/features/record/find.feature index b67938e142a..40e37a6a387 100644 --- a/features/record/find.feature +++ b/features/record/find.feature @@ -16,7 +16,7 @@ Feature: Scoped Finds As an ORM user - I want to use an expressive interface + I want to use an expressive interface So that I can find records. @query @where diff --git a/features/record/optimistic_locking.feature b/features/record/optimistic_locking.feature index 0a4a39f35f8..8456d6744cc 100644 --- a/features/record/optimistic_locking.feature +++ b/features/record/optimistic_locking.feature @@ -16,9 +16,9 @@ Feature: Optimistic Locking Allows multiple users to access the same record for edits - Assumes a minimum of conflicts with the data. - It does this by checking whether another process has made changes to a - record since it was opened, a SimpleModel::StaleObjectError is thrown + Assumes a minimum of conflicts with the data. + It does this by checking whether another process has made changes to a + record since it was opened, a SimpleModel::StaleObjectError is thrown if that has occurred and the update is ignored. Scenario: Optimistic locking ads version tracking column diff --git a/features/record/scope.feature b/features/record/scope.feature index 7486ae514d0..234bc0c3aaa 100644 --- a/features/record/scope.feature +++ b/features/record/scope.feature @@ -16,7 +16,7 @@ Feature: Scoped Finds As an ORM user - I want to use an expressive interface + I want to use an expressive interface So that I can find records. @where @@ -60,7 +60,7 @@ Feature: Scoped Finds | condition | name = 'joe' | @where @wip - Scenario: Using named integer attributes in where condition hashes should + Scenario: Using named integer attributes in where condition hashes should properly pad the numeric value. Given I configure the example class with: """ diff --git a/features/record/validations.feature b/features/record/validations.feature index 463ba1768d9..775a3052938 100644 --- a/features/record/validations.feature +++ b/features/record/validations.feature @@ -162,7 +162,7 @@ Feature: Validations """ string_attr :phone_numbers, :set => true string_attr :username - validates_count_of :phone_numbers, + validates_count_of :phone_numbers, :within => 1..2, :too_many => "has too many numbers (maximum is 2)" """ diff --git a/features/s3/high_level/lifecycle_configuration.feature b/features/s3/high_level/lifecycle_configuration.feature index 39be4c6dc1d..befbdd28fe6 100644 --- a/features/s3/high_level/lifecycle_configuration.feature +++ b/features/s3/high_level/lifecycle_configuration.feature @@ -15,7 +15,7 @@ @s3 @lifecycle_configuration Feature: Bucket Lifecycle Configuration - Scenario: + Scenario: Given I create a new bucket Then the lifecycle configuration should have 0 rules diff --git a/features/s3/high_level/step_definitions/lifecycle_configuration.rb b/features/s3/high_level/step_definitions/lifecycle_configuration.rb index 09e505ec632..2fffb76e99b 100644 --- a/features/s3/high_level/step_definitions/lifecycle_configuration.rb +++ b/features/s3/high_level/step_definitions/lifecycle_configuration.rb @@ -34,7 +34,7 @@ :exp => (Integer(h['EXP']) rescue Date.parse(h['EXP']) rescue nil) || 0, :glacier => (Integer(h['GLACIER']) rescue Date.parse(h['GLACIER']) rescue nil) || 0 }} - + rules = @bucket.lifecycle_configuration.rules.map {|r| { :id => r.id, :prefix => r.prefix, :status => r.status, :exp => r.expiration_time || 0, :glacier => r.glacier_transition_time || 0 diff --git a/features/s3/high_level/step_definitions/post.rb b/features/s3/high_level/step_definitions/post.rb index 31b44b96d65..bedc10ab603 100644 --- a/features/s3/high_level/step_definitions/post.rb +++ b/features/s3/high_level/step_definitions/post.rb @@ -137,8 +137,8 @@ def execute_post(form, opts = {}) resp = http.request(req) @result = resp - unless - resp.kind_of?(Net::HTTPSuccess) or + unless + resp.kind_of?(Net::HTTPSuccess) or resp.kind_of?(Net::HTTPRedirection) or resp.kind_of?(Net::HTTPForbidden) then diff --git a/features/s3/high_level/tree.feature b/features/s3/high_level/tree.feature index 5c054474e98..5ae7e3d2ee1 100644 --- a/features/s3/high_level/tree.feature +++ b/features/s3/high_level/tree.feature @@ -27,7 +27,7 @@ Feature: Bucket as tree | photos/2009/friends.jpg | | photos/2010/family.jpg | | README | - When I access the bucket as a tree + When I access the bucket as a tree Then I should receive leaf nodes with the following keys: | key | | README | @@ -47,7 +47,7 @@ Feature: Bucket as tree | photos-2010-family.jpg | | README | And I use "-" as the delimiter - When I access the bucket as a tree + When I access the bucket as a tree Then I should receive leaf nodes with the following keys: | key | | README | @@ -67,7 +67,7 @@ Feature: Bucket as tree | photos/2010/family.jpg | | README | And I use "photos/" as the prefix - When I access the bucket as a tree + When I access the bucket as a tree Then I should receive branch nodes with the following prefixes: | prefix | | photos/2009/ | @@ -84,7 +84,7 @@ Feature: Bucket as tree | photos/2010/family.jpg | | README | And I use "photos" as the prefix - When I access the bucket as a tree + When I access the bucket as a tree Then I should receive branch nodes with the following prefixes: | prefix | | photos/2009/ | @@ -98,7 +98,7 @@ Feature: Bucket as tree | abc/xyz | And I use "abc" as the prefix And I choose to not append the delimiter - When I access the bucket as a tree + When I access the bucket as a tree Then I should receive branch nodes with the following prefixes: | prefix | | abc/ | @@ -114,7 +114,7 @@ Feature: Bucket as tree | photos/2010/family.jpg | | README | And I use "photos/" as the prefix - When I access the bucket as a tree + When I access the bucket as a tree Then the branch prefixed "photos/2009/" should have leaves with keys: | key | | photos/2009/family.jpg | diff --git a/features/s3/low_level/step_definitions/versions.rb b/features/s3/low_level/step_definitions/versions.rb index 7682b42b8a4..5ef9614d02c 100644 --- a/features/s3/low_level/step_definitions/versions.rb +++ b/features/s3/low_level/step_definitions/versions.rb @@ -12,13 +12,13 @@ # language governing permissions and limitations under the License. Given /^ my account has a versioned bucket in it$/ do - step "I call create_bucket" + step "I call create_bucket" step "I enable versioning for the bucket" end When /^I call set_bucket_versioning with "([^\"]*)"$/ do |state| @s3_client.set_bucket_versioning( - :bucket_name => @bucket_name, + :bucket_name => @bucket_name, :state => state.to_sym) end diff --git a/features/simple_db/high_level/items.feature b/features/simple_db/high_level/items.feature index 87691ccaf61..0f71a254b64 100644 --- a/features/simple_db/high_level/items.feature +++ b/features/simple_db/high_level/items.feature @@ -102,7 +102,7 @@ Feature: SimpleDB Items When I set the "colors" attribute of "car" to "blue": Then the "colors" attribute of "car" item should eventually be: | value | - | blue | + | blue | And a request should have been made like: | TYPE | NAME | VALUE | | param | Action | PutAttributes | @@ -201,8 +201,8 @@ Feature: SimpleDB Items And I add the value "100" to the "price" attribute of "car" Then the "car" item should eventually have attributes named: | name | - | colors | - | price | + | colors | + | price | Scenario: Deleting an item Given I add the following values to the "colors" attribute of "car" diff --git a/features/simple_db/high_level/step_definitions/items.rb b/features/simple_db/high_level/step_definitions/items.rb index cf620a8b3c1..560767b0de1 100644 --- a/features/simple_db/high_level/step_definitions/items.rb +++ b/features/simple_db/high_level/step_definitions/items.rb @@ -60,7 +60,7 @@ Then /^the "([^\"]*)" item should eventually have attributes named:$/ do |item_name,table| attributes = @domain.items[item_name].attributes - + names = [] attributes.each(:consistent_read => true) do |attribute| names << attribute.name @@ -87,13 +87,13 @@ end When /^I (add|set) the following attributes to "([^\"]*)"$/ do |add_or_set, item_name, table| - + attributes_hash = {} table.hashes.each do |hash| attributes_hash[hash['name']] ||= [] attributes_hash[hash['name']] << hash['value'] end - + @domain.items[item_name].attributes.send(add_or_set, attributes_hash) end diff --git a/features/simple_db/high_level/step_definitions/select.rb b/features/simple_db/high_level/step_definitions/select.rb index 8409431a0b2..1670d787095 100644 --- a/features/simple_db/high_level/step_definitions/select.rb +++ b/features/simple_db/high_level/step_definitions/select.rb @@ -123,7 +123,7 @@ When /^I enumerate items with a limit of (\d+) and batch size of (\d+)$/ do |limit, batch_size| @domain.items.each( - :limit => limit, + :limit => limit, :batch_size => batch_size, :consistent_read => true ) {|i|} @@ -131,7 +131,7 @@ When /^I select item data with a limit of (\d+) and batch size of (\d+)$/ do |limit, batch_size| @domain.items.select(:all, - :limit => limit, + :limit => limit, :batch_size => batch_size, :consistent_read => true ) {|i|} diff --git a/features/simple_email_service/email_addresses.feature b/features/simple_email_service/email_addresses.feature index 23333cf4a19..e8c178b48f9 100644 --- a/features/simple_email_service/email_addresses.feature +++ b/features/simple_email_service/email_addresses.feature @@ -18,19 +18,19 @@ Feature: Managing SES email addresses I want to be able to create, list and delete verified email addresses. Scenario: Verify a new email address - When I ask to verify the email address "foo@bar.com" + When I ask to verify the email address "foo@bar.com" Then a request should have been made like: | TYPE | NAME | VALUE | | param | Action | VerifyEmailAddress | | param | EmailAddress | foo@bar.com | - + Scenario: Delete a verified email address - When I ask to delete the email address "foo@bar.com" + When I ask to delete the email address "foo@bar.com" Then a request should have been made like: | TYPE | NAME | VALUE | | param | Action | DeleteVerifiedEmailAddress | | param | EmailAddress | foo@bar.com | - + Scenario: List verified email addresses When I enumerate verified email addresses Then a request should have been made like: diff --git a/features/simple_email_service/send.feature b/features/simple_email_service/send.feature index 8feea72cdb4..fe1e83d8430 100644 --- a/features/simple_email_service/send.feature +++ b/features/simple_email_service/send.feature @@ -27,7 +27,7 @@ Feature: Sending email with SES | param_match | Destination.ToAddresses.member.1 | noreply@example.com | | param_match | Source | noreply@example.com | - Scenario: Send a raw email + Scenario: Send a raw email When I send a raw email like: """ Subject: A Sample Email @@ -43,7 +43,7 @@ Feature: Sending email with SES | TYPE | NAME | VALUE | | param | Action | SendRawEmail | - Scenario: Send a raw email + Scenario: Send a raw email When I send a raw email to "foo@bar.com" from "bar@foo.com" like: """ Subject: A Sample Email diff --git a/features/simple_email_service/step_definitions/send.rb b/features/simple_email_service/step_definitions/send.rb index 1c890e19bce..78faa10bf6b 100644 --- a/features/simple_email_service/step_definitions/send.rb +++ b/features/simple_email_service/step_definitions/send.rb @@ -18,7 +18,7 @@ :subject => 'A Sample Email', :from => 'noreply@example.com', :to => 'noreply@example.com', - :body_text => 'sample text') + :body_text => 'sample text') rescue => e e.message.should match(/Email address is not verified/) end diff --git a/features/simple_email_service/step_definitions/ses.rb b/features/simple_email_service/step_definitions/ses.rb index 433d0002126..80403effa80 100644 --- a/features/simple_email_service/step_definitions/ses.rb +++ b/features/simple_email_service/step_definitions/ses.rb @@ -21,7 +21,7 @@ end After("@ses") do - + @created_identities.each do |identity| identity.delete end diff --git a/features/simple_workflow/activity_types.feature b/features/simple_workflow/activity_types.feature index c0aba7cb506..1db80cea06a 100644 --- a/features/simple_workflow/activity_types.feature +++ b/features/simple_workflow/activity_types.feature @@ -19,7 +19,7 @@ Feature: Managing Activity Types in Simple Workflow Scenario: Register a activity type Given I register a simple workflow domain When I register a activity type named "foo" with the version "bar" with: - | ATTR_NAME | VALUE | + | ATTR_NAME | VALUE | | default_task_heartbeat_timeout | 3000 | | default_task_list | task-list | | description | desc | @@ -28,7 +28,7 @@ Feature: Managing Activity Types in Simple Workflow | default_task_start_to_close_timeout | 4000 | And I get the activity type by name and version Then the activity type should have the following attributes - | ATTR_NAME | VALUE | + | ATTR_NAME | VALUE | | default_task_heartbeat_timeout | 3000 | | default_task_list | task-list | | description | desc | diff --git a/features/simple_workflow/step_definitions/activity_types.rb b/features/simple_workflow/step_definitions/activity_types.rb index 810d004ae64..3edb88a0b95 100644 --- a/features/simple_workflow/step_definitions/activity_types.rb +++ b/features/simple_workflow/step_definitions/activity_types.rb @@ -62,7 +62,7 @@ end Then /^deprecated activity types should include the activity type$/ do - eventually do + eventually do @domain.activity_types.deprecated.should include(@activity_type) end end diff --git a/features/simple_workflow/step_definitions/decision_tasks.rb b/features/simple_workflow/step_definitions/decision_tasks.rb index 4b18e8456ea..60b4e09e411 100644 --- a/features/simple_workflow/step_definitions/decision_tasks.rb +++ b/features/simple_workflow/step_definitions/decision_tasks.rb @@ -16,8 +16,8 @@ end Then /^the count of decision tasks for "([^"]*)" should be (\d+)$/ do |task_list,count| - eventually do - @domain.decision_tasks.count(task_list).should == + eventually do + @domain.decision_tasks.count(task_list).should == AWS::SimpleWorkflow::Count.new(count.to_i, false) end end diff --git a/features/simple_workflow/step_definitions/workflow_executions.rb b/features/simple_workflow/step_definitions/workflow_executions.rb index b3052165190..16db6c350db 100644 --- a/features/simple_workflow/step_definitions/workflow_executions.rb +++ b/features/simple_workflow/step_definitions/workflow_executions.rb @@ -19,7 +19,7 @@ Then /^there should eventually be (\d+) workflow execution of the workflow type$/ do |count| eventually do - @workflow_type.count_executions.should == + @workflow_type.count_executions.should == AWS::SimpleWorkflow::Count.new(count.to_i, false) end end @@ -29,11 +29,11 @@ end Then /^the execution history should have a "([^"]*)" signal with "([^"]*)"$/ do |signal_name, input| - signal_event_found = false + signal_event_found = false @workflow_execution.history_events.each do |event| - if + if event.event_type == 'WorkflowExecutionSignaled' and - event.attributes.signal_name == signal_name and + event.attributes.signal_name == signal_name and event.attributes.input == input then signal_event_found = true @@ -53,7 +53,7 @@ end Then /^the workflow type should have (\d+) workflow executions$/ do |count| - @domain.workflow_executions.count.should == + @domain.workflow_executions.count.should == AWS::SimpleWorkflow::Count.new(count.to_i, false) end diff --git a/features/simple_workflow/step_definitions/workflow_types.rb b/features/simple_workflow/step_definitions/workflow_types.rb index 76e369468eb..8e72e33478e 100644 --- a/features/simple_workflow/step_definitions/workflow_types.rb +++ b/features/simple_workflow/step_definitions/workflow_types.rb @@ -61,7 +61,7 @@ end Then /^deprecated workflow types should include the workflow type$/ do - eventually do + eventually do @domain.workflow_types.deprecated.should include(@workflow_type) end end diff --git a/features/simple_workflow/workflow_types.feature b/features/simple_workflow/workflow_types.feature index 6ff102da335..a680b36710d 100644 --- a/features/simple_workflow/workflow_types.feature +++ b/features/simple_workflow/workflow_types.feature @@ -19,7 +19,7 @@ Feature: Managing Workflow Types in Simple Workflow Scenario: Register a workflow type Given I register a simple workflow domain When I register a workflow type named "foo" with the version "bar" with: - | ATTR_NAME | VALUE | + | ATTR_NAME | VALUE | | default_child_policy | terminate | | default_execution_start_to_close_timeout | 12345678 | | default_task_start_to_close_timeout | none | @@ -27,7 +27,7 @@ Feature: Managing Workflow Types in Simple Workflow | description | desc | And I get the workflow type by name and version Then the workflow type should have the following attributes - | ATTR_NAME | VALUE | + | ATTR_NAME | VALUE | | default_child_policy | terminate | | default_execution_start_to_close_timeout | 12345678 | | default_task_start_to_close_timeout | none | diff --git a/features/sns/step_definitions/topics.rb b/features/sns/step_definitions/topics.rb index 6f876b74555..4b98fcb4348 100644 --- a/features/sns/step_definitions/topics.rb +++ b/features/sns/step_definitions/topics.rb @@ -23,7 +23,7 @@ Then /^the topic should have the correct display name$/ do @topic.name.should == @topic_name -end +end When /^I delete the topic$/ do @topic.delete diff --git a/features/sns/topics.feature b/features/sns/topics.feature index 2d043c27b85..e4ec1dee92d 100644 --- a/features/sns/topics.feature +++ b/features/sns/topics.feature @@ -69,7 +69,7 @@ Feature: Managing SNS Topics Then The queue should eventually have the message @delivery_policies - Scenario: + Scenario: Given I create an SNS topic And I set the delivery policy to: """ diff --git a/features/sqs/queues.feature b/features/sqs/queues.feature index 2bea10c690b..fd1cfde118d 100644 --- a/features/sqs/queues.feature +++ b/features/sqs/queues.feature @@ -128,7 +128,7 @@ Feature: SQS Queues | abc | | mno | | xyz | - When I receive 3 messages + When I receive 3 messages And I delete the messages Then a request should have been made like: | TYPE | NAME | VALUE | diff --git a/features/sqs/step_definitions/policies.rb b/features/sqs/step_definitions/policies.rb index d9d171e1353..815f9dc9e78 100644 --- a/features/sqs/step_definitions/policies.rb +++ b/features/sqs/step_definitions/policies.rb @@ -14,8 +14,8 @@ When /^I set the queue policy$/ do @policy = AWS::SQS::Policy.new @policy.allow( - :actions => :any, - :principles => ["arn:aws:iam::681294939609:root"], + :actions => :any, + :principles => ["arn:aws:iam::681294939609:root"], :resources => @queue) @queue.policy = @policy end diff --git a/features/step_definitions.rb b/features/step_definitions.rb index 1a9d9ed4c86..bd21ede1e1d 100644 --- a/features/step_definitions.rb +++ b/features/step_definitions.rb @@ -194,7 +194,7 @@ def requests_matching requests, table end def table_formatted_requests requests -# tables = requests.collect do |req| +# tables = requests.collect do |req| # table = [] # table << "|TYPE|NAME|VALUE|" # table << "|http|verb|#{req.http_method}|" @@ -214,7 +214,7 @@ def table_formatted_requests requests # Cucumber::Ast::Table.parse(table.join("\n"), nil, nil) # end # tables.collect{|t| t.to_s(:color => false, :prefixes => Hash.new('')) } - tables = requests.collect do |req| + tables = requests.collect do |req| table = [] if req.headers["content-type"] and req.headers["content-type"].include?("json") table << %w(TYPE NAME VALUE) diff --git a/features/support/common.rb b/features/support/common.rb index 51bd7515e06..d622a85971f 100644 --- a/features/support/common.rb +++ b/features/support/common.rb @@ -93,14 +93,14 @@ class << handler }) list.versions.each do |version| @s3_client.delete_object({ - :bucket_name => bucket_name, + :bucket_name => bucket_name, :key => version.key, :version_id => version.version_id, :endpoint => endpoint, }) end end while list.truncated? - rescue + rescue # ignore end @s3_client.delete_bucket(:bucket_name => bucket_name, :endpoint => endpoint) rescue nil diff --git a/lib/aws/auto_scaling/activity.rb b/lib/aws/auto_scaling/activity.rb index 62a2709a82c..b89be556304 100644 --- a/lib/aws/auto_scaling/activity.rb +++ b/lib/aws/auto_scaling/activity.rb @@ -30,7 +30,7 @@ class AutoScaling # @attr_reader [Integer] progress # # @attr_reader [nil,String] status_code - # + # # @attr_reader [nil,String] status_message # class Activity < Core::Resource diff --git a/lib/aws/auto_scaling/activity_collection.rb b/lib/aws/auto_scaling/activity_collection.rb index d9d7b5d1656..73e0ff24801 100644 --- a/lib/aws/auto_scaling/activity_collection.rb +++ b/lib/aws/auto_scaling/activity_collection.rb @@ -33,11 +33,11 @@ class AutoScaling # If you know the id of an activity you can get a reference to it: # # activity = auto_scaling.activities['activity-id'] - # + # class ActivityCollection include Core::Collection::WithLimitAndNextToken - + # @private def initialize options = {} @group = options[:group] @@ -53,7 +53,7 @@ def initialize options = {} def [] activity_id Activity.new(activity_id, :config => config) end - + protected def _each_item next_token, limit, options = {}, &block @@ -64,13 +64,13 @@ def _each_item next_token, limit, options = {}, &block resp = client.describe_scaling_activities(options) resp.activities.each do |details| - + activity = Activity.new_from( - :describe_scaling_activities, details, + :describe_scaling_activities, details, details.activity_id, :config => config) yield(activity) - + end resp.data[:next_token] diff --git a/lib/aws/auto_scaling/group.rb b/lib/aws/auto_scaling/group.rb index db321d976df..e1e461dd265 100644 --- a/lib/aws/auto_scaling/group.rb +++ b/lib/aws/auto_scaling/group.rb @@ -19,7 +19,7 @@ class AutoScaling # @attr_reader [Array] availability_zone_names # # @attr_reader [Time] created_time - # + # # @attr_reader [Integer] default_cooldown # # @attr_reader [Integer] desired_capacity @@ -41,7 +41,7 @@ class AutoScaling # # @attr_reader [String,nil] placement_group # - # @attr_reader [Hash] suspended_processes A hash of suspended process + # @attr_reader [Hash] suspended_processes A hash of suspended process # names (keys) and reasons (values). # class Group < Core::Resource @@ -70,7 +70,7 @@ def initialize name, options = {} attribute :enabled_metrics do translates_output do |metrics| - metrics.inject({}) do |hash,metric| + metrics.inject({}) do |hash,metric| hash.merge(metric.metric => metric.granularity) end end @@ -81,7 +81,7 @@ def initialize name, options = {} attribute :health_check_type, :to_sym => true attribute :instances - + protected :instances attribute :launch_configuration_name @@ -151,9 +151,9 @@ def activities def auto_scaling_instances instances.collect do |details| Instance.new_from( - :describe_auto_scaling_groups, + :describe_auto_scaling_groups, details, - details.instance_id, + details.instance_id, :auto_scaling_group_name => name, # not provided by the response :config => config) end @@ -167,8 +167,8 @@ def auto_scaling_instances # puts instance.id # end # - # @return [EC2::InstanceCollection] Returns an instance collection - # (without making a request) that represents the instances + # @return [EC2::InstanceCollection] Returns an instance collection + # (without making a request) that represents the instances # belonging to this Auto Scaling group. # def ec2_instances @@ -197,13 +197,13 @@ def load_balancers end end - # Adjusts the desired size of the Auto Scaling group by initiating - # scaling activities. When reducing the size of the group, it is - # not possible to define which Amazon EC2 instances will be - # terminated. This applies to any Auto Scaling decisions that might + # Adjusts the desired size of the Auto Scaling group by initiating + # scaling activities. When reducing the size of the group, it is + # not possible to define which Amazon EC2 instances will be + # terminated. This applies to any Auto Scaling decisions that might # result in terminating instances. # - # @param [Integer] capacity The new capacity setting for this Auto + # @param [Integer] capacity The new capacity setting for this Auto # Scaling group. # # @param [Hash] options @@ -225,7 +225,7 @@ def set_desired_capacity capacity, options = {} # # # suspend two processes by name # auto_scaling_group.suspend_processes 'Launch', 'AZRebalance' - # + # # @param [Array] processes A list of process to suspend. # # @return [nil] @@ -248,7 +248,7 @@ def suspend_all_processes # # # resume two processes by name # auto_scaling_group.suspend_processes 'Launch', 'AZRebalance' - # + # # @param [Array] processes A list of process to resume. # # @return [nil] @@ -310,7 +310,7 @@ def disable_all_metrics_collection # def update options = {} - group_opts = group_options(options) + group_opts = group_options(options) # tags must be updated using a separate request from the # other attributes, *sigh* @@ -375,7 +375,7 @@ def delete_all_tags # @param [Hash] options # # @option options [Boolean] :force (false) When true, the Auto Scaling - # group will be deleted along with all instances associated with + # group will be deleted along with all instances associated with # the group, without waiting for all instances to be terminated. # # @return [nil] @@ -388,8 +388,8 @@ def delete options = {} nil end - # Deletes the Auto Scaling group along with all instances - # associated with the group, without waiting for all instances + # Deletes the Auto Scaling group along with all instances + # associated with the group, without waiting for all instances # to be terminated. # @return [nil] def delete! @@ -401,7 +401,7 @@ def delete! def exists? client_opts = {} client_opts[:auto_scaling_group_names] = [name] - resp = client.describe_auto_scaling_groups(client_opts) + resp = client.describe_auto_scaling_groups(client_opts) !resp.auto_scaling_groups.empty? end @@ -412,7 +412,7 @@ def resource_identifiers end def get_resource attr_name = nil - client.describe_auto_scaling_groups(:auto_scaling_group_names => [name]) + client.describe_auto_scaling_groups(:auto_scaling_group_names => [name]) end end diff --git a/lib/aws/auto_scaling/group_collection.rb b/lib/aws/auto_scaling/group_collection.rb index 4bb82bc6bab..5e71ea33d19 100644 --- a/lib/aws/auto_scaling/group_collection.rb +++ b/lib/aws/auto_scaling/group_collection.rb @@ -21,13 +21,13 @@ class GroupCollection # Creates an Auto Scaling Group. # - # group = auto_scaling.groups.create('group-name', + # group = auto_scaling.groups.create('group-name', # :launch_configuration => 'launch-config-name', # :availability_zones => %(us-west-2a us-west-2b), # :min_size => 1, # :max_size => 4) - # - # @param [String] name The name of the Auto Scaling group. + # + # @param [String] name The name of the Auto Scaling group. # Must be unique within the scope of your AWS account. # # @param [Hash] options @@ -35,7 +35,7 @@ class GroupCollection # @option (see GroupOptions#group_options) # # @option options [Array,Array] :load_balancers - # A list of load balancers to use. This can be an array of + # A list of load balancers to use. This can be an array of # {ELB::LoadBalancer} objects or an array of load balancer names. # # @return [Group] @@ -76,7 +76,7 @@ def _each_item next_token, limit, options = {}, &block resp = client.describe_auto_scaling_groups(options) resp.auto_scaling_groups.each do |details| - + group = Group.new_from( :describe_auto_scaling_groups, details, @@ -84,7 +84,7 @@ def _each_item next_token, limit, options = {}, &block :config => config) yield(group) - + end resp.data[:next_token] diff --git a/lib/aws/auto_scaling/group_options.rb b/lib/aws/auto_scaling/group_options.rb index d7efca392c1..d3c152d5691 100644 --- a/lib/aws/auto_scaling/group_options.rb +++ b/lib/aws/auto_scaling/group_options.rb @@ -36,28 +36,28 @@ module GroupOptions # # @option options [required,Array] :availability_zones # A list of Availability Zones for the Auto Scaling group. - # This can be {EC2::AvailabilityZone} objects or availability + # This can be {EC2::AvailabilityZone} objects or availability # zone names. # - # @option options [Integer] :default_cooldown - # The amount of time, in seconds, after a scaling activity completes + # @option options [Integer] :default_cooldown + # The amount of time, in seconds, after a scaling activity completes # before any further trigger-related scaling activities can start. # # @option options [Integer] :desired_capacity - # The number of Amazon EC2 instances that should be running in + # The number of Amazon EC2 instances that should be running in # the group. # # @option options [Integer] :health_check_grace_period - # Length of time in seconds after a new Amazon EC2 instance comes + # Length of time in seconds after a new Amazon EC2 instance comes # into service that Auto Scaling starts checking its health. # # @option options [Symbol] :health_check_type - # The service you want the health status from, - # Amazon EC2 or Elastic Load Balancer. Valid values are + # The service you want the health status from, + # Amazon EC2 or Elastic Load Balancer. Valid values are # +:ec2+ or +:elb+. # # @option options [String] :placement_group - # Physical location of your cluster placement group created in + # Physical location of your cluster placement group created in # Amazon EC2. For more information about cluster placement group, see # {Using Cluster Instances}[http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/using_cluster_computing.html]. # @@ -66,12 +66,12 @@ module GroupOptions # # * +:key+ - (required,String) The tag name. # * +:value+ - (String) The optional tag value. - # * +:propagate_at_launch+ - (Boolean) Whether or not to propagate + # * +:propagate_at_launch+ - (Boolean) Whether or not to propagate # to instances, defaults to true. # # @option options [Array,Array] :subnets - # A list of subnet identifiers of Amazon Virtual Private Clouds - # (Amazon VPCs). Ensure the subnets' Availability Zones match the + # A list of subnet identifiers of Amazon Virtual Private Clouds + # (Amazon VPCs). Ensure the subnets' Availability Zones match the # Availability Zones specified. # # @return [Hash] @@ -95,11 +95,11 @@ def group_options options group_opts[:tags] = tags_opt(options) if options.key?(:tags) - [ + [ :min_size, :max_size, - :default_cooldown, - :desired_capacity, + :default_cooldown, + :desired_capacity, :health_check_grace_period, :placement_group, ].each do |opt| diff --git a/lib/aws/auto_scaling/instance.rb b/lib/aws/auto_scaling/instance.rb index 121aec6d9f6..e440df230bd 100644 --- a/lib/aws/auto_scaling/instance.rb +++ b/lib/aws/auto_scaling/instance.rb @@ -18,7 +18,7 @@ class AutoScaling # # == Getting Auto Scaling Instances # - # If you know the EC2 instance id, you can use {InstanceCollection#[]} + # If you know the EC2 instance id, you can use {InstanceCollection#[]} # to get the Auto Scaling instance. # # instance = auto_scaling.instances['i-1234578'] @@ -31,7 +31,7 @@ class AutoScaling # # auto_scaling = AWS::AutoScaling.new # auto_scaling.instances.each do |auto_scaling_instance| - # # ... + # # ... # end # # If you want the instances for a single auto scaling group: @@ -70,10 +70,10 @@ def initialize instance_id, options = {} attribute :auto_scaling_group_name, :static => true - attribute :availability_zone_name, + attribute :availability_zone_name, :from => :availability_zone, :static => true - + attribute :health_status attribute :launch_configuration_name, :static => true @@ -116,19 +116,19 @@ def auto_scaling_group def availability_zone EC2::AvailabilityZone.new(availability_zone_name, :config => config) end - + # @return [LaunchConfiguration] def launch_configuration LaunchConfiguration.new(launch_configuration_name, :config => config) end - # @param [String] status Sets the health status of an instance. + # @param [String] status Sets the health status of an instance. # Valid values inculde 'Healthy' and 'Unhealthy' # # @param [Hash] options # - # @option options [Boolean] :respect_grace_period (false) If true, - # this call should respect the grace period associated with + # @option options [Boolean] :respect_grace_period (false) If true, + # this call should respect the grace period associated with # this instance's Auto Scaling group. # # @return [nil] @@ -137,12 +137,12 @@ def set_health status, options = {} client_opts = {} client_opts[:instance_id] = instance_id client_opts[:health_status] = status - client_opts[:should_respect_grace_period] = + client_opts[:should_respect_grace_period] = options[:respect_grace_period] == true client.set_instance_health(client_opts) end - # @return [Boolean] Returns true if there exists an Auto Scaling + # @return [Boolean] Returns true if there exists an Auto Scaling # instance with this instance id. def exists? !get_resource.auto_scaling_instances.empty? @@ -150,8 +150,8 @@ def exists? # Terminates the current Auto Scaling instance. # - # @param [Boolean] decrement_desired_capacity Specifies whether or not - # terminating this instance should also decrement the size of + # @param [Boolean] decrement_desired_capacity Specifies whether or not + # terminating this instance should also decrement the size of # the AutoScalingGroup. # # @return [Activity] Returns an activity that represents the @@ -161,15 +161,15 @@ def terminate decrement_desired_capacity client_opts = {} client_opts[:instance_id] = instance_id - client_opts[:should_decrement_desired_capacity] = + client_opts[:should_decrement_desired_capacity] = decrement_desired_capacity resp = client.terminate_instance_in_auto_scaling_group(client_opts) Activity.new_from( :terminate_instance_in_auto_scaling_group, - resp.activity, - resp.activity.activity_id, + resp.activity, + resp.activity.activity_id, :config => config) end diff --git a/lib/aws/auto_scaling/instance_collection.rb b/lib/aws/auto_scaling/instance_collection.rb index b5e12bfc904..655d894c980 100644 --- a/lib/aws/auto_scaling/instance_collection.rb +++ b/lib/aws/auto_scaling/instance_collection.rb @@ -20,7 +20,7 @@ class AutoScaling # auto_scaling.instances.each do |instance| # # ... # end - # + # # You can also get an Auto Scaling instance by its EC2 instance id. # # auto_scaling_instance = auto_scaling.instances['i-12345678'] @@ -29,7 +29,7 @@ class AutoScaling class InstanceCollection include Core::Collection::WithLimitAndNextToken - + # @param [String] instance_id An {EC2::Instance} id string. # @return [AutoScaling::Instance] def [] instance_id @@ -45,7 +45,7 @@ def _each_item next_token, limit, options = {}, &block resp = client.describe_auto_scaling_instances(options) resp.auto_scaling_instances.each do |details| - + instance = Instance.new_from( :describe_auto_scaling_instances, details, @@ -53,7 +53,7 @@ def _each_item next_token, limit, options = {}, &block :config => config) yield(instance) - + end resp.data[:next_token] end diff --git a/lib/aws/auto_scaling/launch_configuration.rb b/lib/aws/auto_scaling/launch_configuration.rb index 065870d6b41..75e81598573 100644 --- a/lib/aws/auto_scaling/launch_configuration.rb +++ b/lib/aws/auto_scaling/launch_configuration.rb @@ -37,7 +37,7 @@ class AutoScaling # @attr_reader [String,nil] user_data # # @attr_reader [Array] block_device_mappings - # + # # @attr_reader [String] iam_instance_profile # # @attr_reader [String] spot_price @@ -88,8 +88,8 @@ def initialize name, options = {} translates_output{|mappings| mappings.map(&:to_hash) } end - attribute :security_group_details, - :from => :security_groups, + attribute :security_group_details, + :from => :security_groups, :static => true protected :security_group_details diff --git a/lib/aws/auto_scaling/notification_configuration_collection.rb b/lib/aws/auto_scaling/notification_configuration_collection.rb index 63d505291c9..01d70a54627 100644 --- a/lib/aws/auto_scaling/notification_configuration_collection.rb +++ b/lib/aws/auto_scaling/notification_configuration_collection.rb @@ -31,7 +31,7 @@ class AutoScaling # group.notification_configurations.each do |config| # # ... # end - # + # # == Creating Notification Configurations # # You can create a notification configuration like so: @@ -48,7 +48,7 @@ class AutoScaling class NotificationConfigurationCollection include Core::Collection::WithLimitAndNextToken - + # @private def initialize options = {} @group = options[:group] @@ -69,7 +69,7 @@ def initialize options = {} # configuration you need an {SNS::Topic} and an Auto Scaling {Group}. # # auto_scaling.notification_configurations.create( - # :group => 'auto-scaling-group-name', + # :group => 'auto-scaling-group-name', # :topic => 'sns-topic-arn') # # You can also create notification configurations from an Auto Scaling @@ -78,7 +78,7 @@ def initialize options = {} # auto_scaling_group.notification_configurations.create( # :topic => 'sns-topic-arn') # - # You may also pass a list of notification types to publish to the + # You may also pass a list of notification types to publish to the # topic. If you omit this option, then all notification types # will be configured. # @@ -93,7 +93,7 @@ def initialize options = {} # # @param [Hash] options # - # @option options [required,SNS::Topic,String] :topic An {SNS::Topic} + # @option options [required,SNS::Topic,String] :topic An {SNS::Topic} # object or a topic arn string. Notifications will be published # to this topic. # @@ -107,8 +107,8 @@ def initialize options = {} # @return [NotificationConfiguration] # def create options = {} - - topic_arn = options[:topic].is_a?(SNS::Topic) ? + + topic_arn = options[:topic].is_a?(SNS::Topic) ? options[:topic].arn : options[:topic] unless group = @group @@ -130,7 +130,7 @@ def create options = {} end alias_method :put, :create - # @yield [notification_config] + # @yield [notification_config] # @yieldparam [NotificationConfiguration] notification_config def each &block @@ -143,7 +143,7 @@ def each &block # rest in the next page. # # So instead we will request and group them all before yielding. - # + # next_token = nil @@ -169,7 +169,7 @@ def each &block groups.each_pair do |group_name, topics| topics.each_pair do |topic_arn, types| - + notification_config = NotificationConfiguration.new( Group.new(group_name, :config => config), topic_arn, types) diff --git a/lib/aws/auto_scaling/scaling_policy.rb b/lib/aws/auto_scaling/scaling_policy.rb index 426587b55f7..9a80cd44fc5 100644 --- a/lib/aws/auto_scaling/scaling_policy.rb +++ b/lib/aws/auto_scaling/scaling_policy.rb @@ -14,7 +14,7 @@ module AWS class AutoScaling - # + # # @attr_reader [String] arn # # @attr_reader [String] adjustment_type @@ -66,7 +66,7 @@ def initialize auto_scaling_group, policy_name, options = {} attribute :min_adjustment_step populates_from(:describe_policies) do |resp| - resp.scaling_policies.find do |p| + resp.scaling_policies.find do |p| p.policy_name == name and p.auto_scaling_group_name == group.name end @@ -89,7 +89,7 @@ def update options = {} # @param [Hash] options # # @option options [Boolean] :honor_cooldown (false) Set to true if you - # want Auto Scaling to reject this request when the Auto Scaling + # want Auto Scaling to reject this request when the Auto Scaling # group is in cooldown. # # @raise [Errors::ScalingActivityInProgress] diff --git a/lib/aws/auto_scaling/scaling_policy_collection.rb b/lib/aws/auto_scaling/scaling_policy_collection.rb index f62eb05c503..ccb267be10e 100644 --- a/lib/aws/auto_scaling/scaling_policy_collection.rb +++ b/lib/aws/auto_scaling/scaling_policy_collection.rb @@ -17,7 +17,7 @@ class ScalingPolicyCollection include Core::Collection::WithLimitAndNextToken include ScalingPolicyOptions - + def initialize auto_scaling_group, options = {} @group = auto_scaling_group super @@ -28,7 +28,7 @@ def initialize auto_scaling_group, options = {} alias_method :auto_scaling_group, :group - + # @param [String] name The name of the policy you want to create or update. # @param (see ScalingPolicyOptions#scaling_policy_options) # @option (see ScalingPolicyOptions#scaling_policy_options) @@ -45,7 +45,7 @@ def create name, options = {} def [] policy_name ScalingPolicy.new(group, policy_name) end - + protected def _each_item next_token, limit, options = {}, &block @@ -56,13 +56,13 @@ def _each_item next_token, limit, options = {}, &block resp = client.describe_policies(options) resp.scaling_policies.each do |details| - + scaling_policy = ScalingPolicy.new_from( - :describe_policies, details, + :describe_policies, details, group, details.policy_name) yield(scaling_policy) - + end resp.data[:next_token] end diff --git a/lib/aws/auto_scaling/scaling_policy_options.rb b/lib/aws/auto_scaling/scaling_policy_options.rb index e5110f2e029..b7708c80107 100644 --- a/lib/aws/auto_scaling/scaling_policy_options.rb +++ b/lib/aws/auto_scaling/scaling_policy_options.rb @@ -30,14 +30,14 @@ module ScalingPolicyOptions # * 'PercentChangeInCapacity' # # @option options [required,Integer] :scaling_adjustment The number of - # instances by which to scale. +:adjustment_type+ determines the + # instances by which to scale. +:adjustment_type+ determines the # interpretation of this umber (e.g., as an absolute number or as a - # percentage of the existing Auto Scaling group size). A positive - # increment adds to the current capacity and a negative value + # percentage of the existing Auto Scaling group size). A positive + # increment adds to the current capacity and a negative value # removes from the current capacity. # # @option options [Integer] :cooldown The amount of time, in seconds, - # after a scaling activity completes before any further + # after a scaling activity completes before any further # trigger-related scaling activities can start. # # @option options [Integer] :min_adjustment_step diff --git a/lib/aws/auto_scaling/scheduled_action.rb b/lib/aws/auto_scaling/scheduled_action.rb index df13606fcda..b4f61f9abbd 100644 --- a/lib/aws/auto_scaling/scheduled_action.rb +++ b/lib/aws/auto_scaling/scheduled_action.rb @@ -71,7 +71,7 @@ def group end # Updates the scheduled action. If you omit an option, - # the corresponding value remains unchanged in the Auto + # the corresponding value remains unchanged in the Auto # Scaling group. # # @param [Hash] options @@ -96,7 +96,7 @@ def update options = {} client_opts[:scheduled_action_name] = name client_opts[:auto_scaling_group_name] = auto_scaling_group_name - # convert these options to timestamps + # convert these options to timestamps [:start_time, :end_time].each do |opt| if client_opts[opt].is_a?(Time) client_opts[opt] = client_opts[opt].iso8601 @@ -112,7 +112,7 @@ def update options = {} # @return [Boolean] def exists? - client_opts = {} + client_opts = {} client_opts[:scheduled_action_names] = [name] resp = client.describe_scheduled_actions(client_opts) !resp.scheduled_update_group_actions.empty? diff --git a/lib/aws/auto_scaling/scheduled_action_collection.rb b/lib/aws/auto_scaling/scheduled_action_collection.rb index 3cf87299cad..7cb0cad3185 100644 --- a/lib/aws/auto_scaling/scheduled_action_collection.rb +++ b/lib/aws/auto_scaling/scheduled_action_collection.rb @@ -19,14 +19,14 @@ class AutoScaling class ScheduledActionCollection include Core::Collection::WithLimitAndNextToken - + # @private def initialize options = {} @filters = options[:filters] || {} super end - # Creates a scheduled scaling action for an Auto Scaling group. + # Creates a scheduled scaling action for an Auto Scaling group. # If you leave a parameter unspecified, the corresponding attribute # remains unchanged in the group. # @@ -55,14 +55,14 @@ def initialize options = {} # @option options [String] :recurrence # # @option options [Time] :start_time - # + # # @option options [Time] :end_time # # @return [ScheduledAction] # def create name, options = {} - - scheduled_action = ScheduledAction.new(name, + + scheduled_action = ScheduledAction.new(name, :auto_scaling_group_name => auto_scaling_group_name_opt(options), :config => config) @@ -98,25 +98,25 @@ def [] name # filter(:end_time => Time.now) # # actions.each {|scheduled_action| ... } - # + # # @param [Hash] filters # # @option filters [Group,String] :group # # @option filters [Array] :scheduled_actions - # A list of scheduled actions to be described. If this list is - # omitted, all scheduled actions are described. The list of - # requested scheduled actions cannot contain more than 50 items. - # If an Auto Scaling group name is provided, - # the results are limited to that group. If unknown scheduled + # A list of scheduled actions to be described. If this list is + # omitted, all scheduled actions are described. The list of + # requested scheduled actions cannot contain more than 50 items. + # If an Auto Scaling group name is provided, + # the results are limited to that group. If unknown scheduled # actions are requested, they are ignored with no error. # # @option options [Time,String] :start_time The earliest scheduled - # start time to return. If +:scheduled_actions+ is provided, + # start time to return. If +:scheduled_actions+ is provided, # this field will be ignored. Should be a Time object or # an iso8601 string. # - # @option filters [Time,String] :end_time + # @option filters [Time,String] :end_time # # @return [ScheduledActionCollection] Returns a scheduled action # collection that will filter the actions returned by the @@ -175,7 +175,7 @@ def _each_item next_token, limit, options = {}, &block resp = client.describe_scheduled_actions(options.merge(@filters)) resp.scheduled_update_group_actions.each do |details| - + scheduled_action = ScheduledAction.new_from( :describe_scheduled_actions, details, @@ -183,7 +183,7 @@ def _each_item next_token, limit, options = {}, &block :config => config) yield(scheduled_action) - + end resp.data[:next_token] diff --git a/lib/aws/auto_scaling/tag_collection.rb b/lib/aws/auto_scaling/tag_collection.rb index a10231b19a8..67a57b8a26b 100644 --- a/lib/aws/auto_scaling/tag_collection.rb +++ b/lib/aws/auto_scaling/tag_collection.rb @@ -56,7 +56,7 @@ class AutoScaling # # # updating a group's tags # group.update(:tags => tags) - # + # class TagCollection include Core::Collection::WithLimitAndNextToken @@ -66,12 +66,12 @@ def initialize options = {} @filters = options.delete(:filters) || [] super end - + # Filters the tags by the given filter name and value(s). # # # return tags with the key "role" and the value "webserver" # auto_scaling.tags.filter(:key, 'role').filer(:value, 'webserver') - # + # # @param [Symbol] name Valid filter names include: # # * :key diff --git a/lib/aws/cloud_formation.rb b/lib/aws/cloud_formation.rb index baac1304c25..2a868374843 100644 --- a/lib/aws/cloud_formation.rb +++ b/lib/aws/cloud_formation.rb @@ -65,7 +65,7 @@ module AWS # with capabilities and parameters. # # == Getting a Stack - # + # # Given a name, you can fetch a {Stack}. # # stack = cfm.stacks['stack-name'] @@ -126,7 +126,7 @@ module AWS # res = stack.resources['logical-resource-id'] # puts res.physical_resource_id # - # If you need a stack resource, but only have its physical resource + # If you need a stack resource, but only have its physical resource # id, then you can call {CloudFormation#stack_resource}. # # stack_resource = cfm.stack_resource('physical-resource-id') @@ -173,7 +173,7 @@ def stack_summaries end # Returns a stack resource with the given physical resource - # id. + # id. # # resource = cfm.stack_resource('i-123456789') # @@ -230,18 +230,18 @@ def stack_resource *args # * +:message+ # # @return [Hash] - # + # def validate_template template begin client_opts = {} client_opts[:template] = template - apply_template(client_opts) + apply_template(client_opts) client.validate_template(client_opts).data rescue CloudFormation::Errors::ValidationError => e - results = {} + results = {} results[:code] = e.code results[:message] = e.message results @@ -251,19 +251,19 @@ def validate_template template # @param (see Stack#template=) # - # @param [Hash] parameters A hash that specifies the input + # @param [Hash] parameters A hash that specifies the input # parameters for the template. # - # @return [String] Returns a URL to the AWS Simple Monthly Calculator - # with a query string that describes the resources required to run + # @return [String] Returns a URL to the AWS Simple Monthly Calculator + # with a query string that describes the resources required to run # the template. # def estimate_template_cost template, parameters = {} client_opts = {} client_opts[:template] = template client_opts[:parameters] = parameters - apply_template(client_opts) - apply_parameters(client_opts) + apply_template(client_opts) + apply_parameters(client_opts) client.estimate_template_cost(client_opts).url end diff --git a/lib/aws/cloud_formation/stack.rb b/lib/aws/cloud_formation/stack.rb index e9df1f6cebd..49e5d4bd7bb 100644 --- a/lib/aws/cloud_formation/stack.rb +++ b/lib/aws/cloud_formation/stack.rb @@ -15,25 +15,25 @@ module AWS class CloudFormation - # @attr_reader [String] template Returns the stack's template as a JSON + # @attr_reader [String] template Returns the stack's template as a JSON # string. # # @attr_reader [Time] creation_time The time the stack was created. # - # @attr_reader [Time,nil] last_updated_time The time the stack was + # @attr_reader [Time,nil] last_updated_time The time the stack was # last updated. # # @attr_reader [String] stack_id Unique stack identifier. # # @attr_reader [String] status The status of the stack. # - # @attr_reader [String] status_reason Success/Failure message + # @attr_reader [String] status_reason Success/Failure message # associated with the +status+. # - # @attr_reader [Array] capabilities The capabilities + # @attr_reader [Array] capabilities The capabilities # allowed in the stack. # - # @attr_reader [String] description User defined description + # @attr_reader [String] description User defined description # associated with the stack. # # @attr_reader [Boolean] disable_rollback Specifies if the stack @@ -96,7 +96,7 @@ def initialize name, options = {} protected :output_details - describe_attribute :parameters do + describe_attribute :parameters do translates_output do |params| params.inject({}) do |hash,param| hash.merge(param[:parameter_key] => param[:parameter_value]) @@ -139,7 +139,7 @@ def events end # Returns a stack resource collection that enumerates all resources - # for this stack. + # for this stack. # # stack.resources.each do |resource| # puts "#{resource.resource_type}: #{resource.physical_resource_id}" @@ -174,7 +174,7 @@ def resource_summaries # @param [Hash] options # - # @option options [String,URI,S3::S3Object,Object] :template + # @option options [String,URI,S3::S3Object,Object] :template # A new stack template. This may be provided in a number of formats # including: # @@ -188,10 +188,10 @@ def resource_summaries # input parameters of the new stack. # # @option options[Array] :capabilities The list of capabilities - # that you want to allow in the stack. If your stack contains IAM - # resources, you must specify the CAPABILITY_IAM value for this - # parameter; otherwise, this action returns an - # InsufficientCapabilities error. IAM resources are the following: + # that you want to allow in the stack. If your stack contains IAM + # resources, you must specify the CAPABILITY_IAM value for this + # parameter; otherwise, this action returns an + # InsufficientCapabilities error. IAM resources are the following: # # * AWS::IAM::AccessKey # * AWS::IAM::Group @@ -215,7 +215,7 @@ def update options = {} # @return (see CloudFormation#estimate_template_cost) def estimate_template_cost - cloud_formation = CloudFormation.new(:config => config) + cloud_formation = CloudFormation.new(:config => config) cloud_formation.estimate_template_cost(template, parameters) end diff --git a/lib/aws/cloud_formation/stack_event.rb b/lib/aws/cloud_formation/stack_event.rb index 624983a3a1d..8844c7bbb9d 100644 --- a/lib/aws/cloud_formation/stack_event.rb +++ b/lib/aws/cloud_formation/stack_event.rb @@ -40,21 +40,21 @@ def initialize stack, details # @return [String] event_id The unique ID of this event. attr_reader :event_id - # @return [String] The logical name of the resource specified + # @return [String] The logical name of the resource specified # in the template. attr_reader :logical_resource_id - # @return [String] The name or unique identifier associated with the + # @return [String] The name or unique identifier associated with the # physical instance of the resource. attr_reader :physical_resource_id # @return [String] BLOB of the properties used to create the resource. attr_reader :resource_properties - # @return [Symbol] Current status of the resource. + # @return [Symbol] Current status of the resource. attr_reader :resource_status - # @return [String,nil] Success/failure message associated with the + # @return [String,nil] Success/failure message associated with the # resource. attr_reader :resource_status_reason diff --git a/lib/aws/cloud_formation/stack_output.rb b/lib/aws/cloud_formation/stack_output.rb index ce00b428fe0..a0b5e2b6446 100644 --- a/lib/aws/cloud_formation/stack_output.rb +++ b/lib/aws/cloud_formation/stack_output.rb @@ -14,7 +14,7 @@ module AWS class CloudFormation class StackOutput - + # @param [Stack] stack # @param [String] key # @param [String] value diff --git a/lib/aws/cloud_formation/stack_resource.rb b/lib/aws/cloud_formation/stack_resource.rb index f7dec3aac94..02ec78366f2 100644 --- a/lib/aws/cloud_formation/stack_resource.rb +++ b/lib/aws/cloud_formation/stack_resource.rb @@ -21,7 +21,7 @@ class CloudFormation # The name or unique identifier that corresponds to a physical instance # ID of a resource supported by AWS CloudFormation. # - # @attr_reader [Symbol] resource_status + # @attr_reader [Symbol] resource_status # Current status of the resource. # # @attr_reader [String,nil] resource_status_reason @@ -40,7 +40,7 @@ class CloudFormation # When the status was last updated. # # @attr_reader [String,nil] metadata - # The JSON format content of the Metadata attribute declared for the + # The JSON format content of the Metadata attribute declared for the # resource. # class StackResource < Core::Resource @@ -55,7 +55,7 @@ def initialize stack, logical_resource_id, options = {} # @return [Stack] attr_reader :stack - # @return [String] The logical name of the resource specified in + # @return [String] The logical name of the resource specified in # the template. attr_reader :logical_resource_id @@ -85,12 +85,12 @@ def initialize stack, logical_resource_id, options = {} # this operation returns all attributes populates_from(:describe_stack_resource) do |resp| - resp.stack_resource_detail if + resp.stack_resource_detail if resp.stack_resource_detail.logical_resource_id == logical_resource_id end # This method provides ALL attributes except :metadata. The - # :last_updated_timestamp attribute is also provided by + # :last_updated_timestamp attribute is also provided by # a differnt name (:timestamp instead of :last_updated_timestamp). provider(:describe_stack_resources) do |provider| provider.find do |resp| @@ -109,7 +109,7 @@ def resource_identifiers end def get_resource attribute = nil - client.describe_stack_resource(resource_options) + client.describe_stack_resource(resource_options) end end diff --git a/lib/aws/cloud_formation/stack_resource_collection.rb b/lib/aws/cloud_formation/stack_resource_collection.rb index 419e14ebb96..9f161c05a04 100644 --- a/lib/aws/cloud_formation/stack_resource_collection.rb +++ b/lib/aws/cloud_formation/stack_resource_collection.rb @@ -14,7 +14,7 @@ module AWS class CloudFormation - # = StackResourceCollection + # = StackResourceCollection # # This collection represents the resources for a single {Stack}. # You can enumerate resources, or request a specific resource @@ -34,7 +34,7 @@ class CloudFormation # stack.resources.each do |resource| # puts resource.resource_type + " " + resource.physical_resource_id # end - # + # # @example Getting a stack resource by its logical resource id # # resource = stack.resources['web'] @@ -57,7 +57,7 @@ def initialize stack, options = {} # @param [String] logical_resource_id # @return [StackResource] Returns a stack resource with the given # logical resource id. - def [] logical_resource_id + def [] logical_resource_id StackResource.new(stack, logical_resource_id) end @@ -69,9 +69,9 @@ def _each_item options = {} response.stack_resources.each do |details| stack_resource = StackResource.new_from( - :describe_stack_resources, + :describe_stack_resources, details, - self, + self, details.logical_resource_id) yield(stack_resource) diff --git a/lib/aws/cloud_watch/alarm.rb b/lib/aws/cloud_watch/alarm.rb index 8d2c2e714e3..5f19e705d21 100644 --- a/lib/aws/cloud_watch/alarm.rb +++ b/lib/aws/cloud_watch/alarm.rb @@ -20,11 +20,11 @@ class CloudWatch # # @attr_reader [Array] dimensions # - # @attr_reader [Boolean] enabled Indicates whether actions + # @attr_reader [Boolean] enabled Indicates whether actions # should be executed during any changes to the alarm's state. # - # @attr_reader [Array] alarm_actions The list of actions to execute - # when this alarm transitions into an ALARM state from any other + # @attr_reader [Array] alarm_actions The list of actions to execute + # when this alarm transitions into an ALARM state from any other # state. # # @attr_reader [String] arn The Amazon Resource Name (ARN) of the alarm. @@ -34,11 +34,11 @@ class CloudWatch # # @attr_reader [String] description The description for the alarm. # - # @attr_reader [String] comparison_operator The arithmetic operation to - # use when comparing the specified Statistic and Threshold. The + # @attr_reader [String] comparison_operator The arithmetic operation to + # use when comparing the specified Statistic and Threshold. The # specified Statistic value is used as the first operand. # - # @attr_reader [Integer] evaluation_periods The number of periods over + # @attr_reader [Integer] evaluation_periods The number of periods over # which data is compared to the specified threshold. # # @attr_reader [Array] insufficient_data_actions The list of @@ -48,7 +48,7 @@ class CloudWatch # @attr_reader [Array] ok_actions The list of actions to execute # when this alarm transitions into an OK state. # - # @attr_reader [Integer] period The period in seconds over which the + # @attr_reader [Integer] period The period in seconds over which the # statistic is applied. # # @attr_reader [String] state_reason A human-readable explanation for @@ -102,7 +102,7 @@ def initialize alarm_name, options = {} attribute :alarm_configuration_updated_timestamp - alias_method :configuration_updated_timestamp, + alias_method :configuration_updated_timestamp, :alarm_configuration_updated_timestamp attribute :alarm_description @@ -152,18 +152,18 @@ def metric # Updates the metric alarm. # # @option options [String,required] :comparison_operator The arithmetic - # operation to use when comparing the specified Statistic and - # Threshold. The specified Statistic value is used as the first + # operation to use when comparing the specified Statistic and + # Threshold. The specified Statistic value is used as the first # operand. Valid values include: # * 'GreaterThanOrEqualToThreshold' # * 'GreaterThanThreshold' # * 'LessThanThreshold' # * 'LessThanOrEqualToThreshold' - # @option options [Integer,required] :evaluation_periods The number + # @option options [Integer,required] :evaluation_periods The number # of periods over which data is compared to the specified threshold. - # @option options [Integer,required] :period The period in seconds + # @option options [Integer,required] :period The period in seconds # over which the specified statistic is applied. - # @option options [String,required] :statistic The statistic to apply + # @option options [String,required] :statistic The statistic to apply # to the alarm's associated metric. Valid values include: # * 'SampleCount' # * 'Average' @@ -173,28 +173,28 @@ def metric # @option options [Number,required] :threshold The value against which # the specified statistic is compared. # @option options [Array] :insufficient_data_actions - # The list of actions to execute when this alarm transitions into an - # INSUFFICIENT_DATA state from any other state. Each action is - # specified as an Amazon Resource Number (ARN). Currently the only - # action supported is publishing to an Amazon SNS topic or an + # The list of actions to execute when this alarm transitions into an + # INSUFFICIENT_DATA state from any other state. Each action is + # specified as an Amazon Resource Number (ARN). Currently the only + # action supported is publishing to an Amazon SNS topic or an # Amazon Auto Scaling policy. # @option options [Array] :ok_actions The list of actions to # execute when this alarm transitions into an OK state from any # other state. Each action is specified as an Amazon Resource # Number (ARN). Currently the only action supported is publishing to # an Amazon SNS topic or an Amazon Auto Scaling policy. - # @option options [Boolean] :actions_enabled Indicates whether or not - # actions should be executed during any changes to the alarm's + # @option options [Boolean] :actions_enabled Indicates whether or not + # actions should be executed during any changes to the alarm's # state. - # @option options [Array] :alarm_actions The list of actions - # to execute when this alarm transitions into an ALARM state from - # any other state. Each action is specified as an Amazon Resource - # Number (ARN). Currently the only action supported is publishing + # @option options [Array] :alarm_actions The list of actions + # to execute when this alarm transitions into an ALARM state from + # any other state. Each action is specified as an Amazon Resource + # Number (ARN). Currently the only action supported is publishing # to an Amazon SNS topic or an Amazon Auto Scaling policy. # Maximum of 5 alarm actions. - # @option options [String] :alarm_description The description for + # @option options [String] :alarm_description The description for # the alarm. - # @option options [String] :unit The unit for the alarm's associated + # @option options [String] :unit The unit for the alarm's associated # metric. # @return [nil] def update options = {} @@ -238,15 +238,15 @@ def history_items options = {} alias_method :histories, :history_items # Temporarily sets the state of current alarm. - # @param [String] reason The reason that this alarm is set to this + # @param [String] reason The reason that this alarm is set to this # specific state (in human-readable text format). # @param [String] value Valid values include: # * 'OK' # * 'ALARM' # * 'INSUFFICIENT_DATA' # @param [Hash] options - # @option options [String] :state_reason_data The reason that this - # alarm is set to this specific state (in machine-readable JSON + # @option options [String] :state_reason_data The reason that this + # alarm is set to this specific state (in machine-readable JSON # format) # @return [nil] def set_state reason, value, options = {} diff --git a/lib/aws/cloud_watch/alarm_collection.rb b/lib/aws/cloud_watch/alarm_collection.rb index 8daefca870f..4d4bde0b566 100644 --- a/lib/aws/cloud_watch/alarm_collection.rb +++ b/lib/aws/cloud_watch/alarm_collection.rb @@ -15,7 +15,7 @@ module AWS class CloudWatch # = AlarmCollection - # + # # Represents alarms for an AWS account. # # == Getting an alarm by name @@ -59,14 +59,14 @@ def [] alarm_name # Creates an alarm and associates it with the specified metric. # - # @param [String] alarm_name The descriptive name for the alarm. + # @param [String] alarm_name The descriptive name for the alarm. # This name must be unique within the user's AWS account. # @param [Hash] options - # @option options [String,required] :namespace The namespace for the + # @option options [String,required] :namespace The namespace for the # alarm's associated metric. - # @option options [String,required] :metric_name The name for the + # @option options [String,required] :metric_name The name for the # alarm's associated metric. - # @option options [Array] :dimensions The dimensions for the + # @option options [Array] :dimensions The dimensions for the # alarm's associated metric. Each dimension must specify a # +:name+ and a +:value+. # @option (see Alarm#update) @@ -136,7 +136,7 @@ def _each_item next_token, limit, options = {}, &block alarm = Alarm.new_from( :describe_alarms, - details, + details, details[:alarm_name], :config => config) diff --git a/lib/aws/cloud_watch/alarm_history_item_collection.rb b/lib/aws/cloud_watch/alarm_history_item_collection.rb index af972618fe8..70e3502c337 100644 --- a/lib/aws/cloud_watch/alarm_history_item_collection.rb +++ b/lib/aws/cloud_watch/alarm_history_item_collection.rb @@ -63,14 +63,14 @@ def with_type type protected def _each_item next_token, limit, options = {}, &block - + options = @filters.merge(options) options[:max_records] = limit if limit options[:next_token] = next_token if next_token resp = client.describe_alarm_history(options) resp.data[:alarm_history_items].each do |details| - + yield(AlarmHistoryItem.new(details)) end diff --git a/lib/aws/cloud_watch/metric.rb b/lib/aws/cloud_watch/metric.rb index 6a83c83e568..aff19b467fa 100644 --- a/lib/aws/cloud_watch/metric.rb +++ b/lib/aws/cloud_watch/metric.rb @@ -26,7 +26,7 @@ class Metric < Core::Resource # @param [String] namespace The metric namespace. # @param [String] metric_name The metric name. # @param [Hash] options - # @option options [Array] :dimensions An array of dimensions. + # @option options [Array] :dimensions An array of dimensions. # Each hash must have a +:name+ and a +value+ key (with string values). def initialize namespace, metric_name, options = {} @namespace = namespace @@ -56,7 +56,7 @@ def alarms # must pass +:value+ (number) or +:statistic_values+ (hash). # @return [nil] def put_data *metric_data - + metric_opts = {} metric_opts[:metric_name] = metric_name metric_opts[:dimensions] = dimensions unless dimensions.empty? diff --git a/lib/aws/cloud_watch/metric_alarm_collection.rb b/lib/aws/cloud_watch/metric_alarm_collection.rb index 675e1bb9e74..76e638cc143 100644 --- a/lib/aws/cloud_watch/metric_alarm_collection.rb +++ b/lib/aws/cloud_watch/metric_alarm_collection.rb @@ -82,7 +82,7 @@ def create alarm_name, options = {} # # metric.alarms.filter('period', 3600) # - # @example Filtering by statistic + # @example Filtering by statistic # # my_metric = metrics.filter('statistic', 'maximum') # @@ -143,7 +143,7 @@ def _each_item options = {}, &block alarm = Alarm.new_from( :describe_alarms_for_metric, - details, + details, details[:alarm_name], :config => config) diff --git a/lib/aws/cloud_watch/metric_collection.rb b/lib/aws/cloud_watch/metric_collection.rb index d45678f0403..b48966f4320 100644 --- a/lib/aws/cloud_watch/metric_collection.rb +++ b/lib/aws/cloud_watch/metric_collection.rb @@ -101,7 +101,7 @@ def with_dimensions *dimensions dimensions += dimensions.flatten filter(:dimensions, dimensions) end - + protected def _each_item next_token, options = {}, &block @@ -113,7 +113,7 @@ def _each_item next_token, options = {}, &block resp.data[:metrics].each do |details| metric = Metric.new_from( - :list_metrics, details, + :list_metrics, details, details[:namespace], details[:metric_name], details.merge(:config => config)) diff --git a/lib/aws/cloud_watch/metric_statistics.rb b/lib/aws/cloud_watch/metric_statistics.rb index f77f3ea21fe..4a44fde5450 100644 --- a/lib/aws/cloud_watch/metric_statistics.rb +++ b/lib/aws/cloud_watch/metric_statistics.rb @@ -33,7 +33,7 @@ class CloudWatch # end # # @see Core::Collection - # + # class MetricStatistics include Core::Collection::Simple diff --git a/lib/aws/core/async_handle.rb b/lib/aws/core/async_handle.rb index 4f1034f9f59..fd2537f1c53 100644 --- a/lib/aws/core/async_handle.rb +++ b/lib/aws/core/async_handle.rb @@ -13,21 +13,21 @@ module AWS module Core - + # Mixin that provides a generic callback facility for asynchronous # tasks that can either succeed or fail. module AsyncHandle - + # Called to signal success and fire off the success and complete callbacks. def signal_success __send_signal(:success) end - + # Called to signal failure and fire off the failure and complete callbacks. def signal_failure __send_signal(:failure) end - + # Registers a callback to be called on successful completion of # the task. # @@ -42,7 +42,7 @@ def on_success(&block) (@_async_callbacks ||= []) << { :success => block } end end - + # Registers a callback to be called when the task fails. # # handle.on_failure { puts "It didn't work!" } @@ -56,7 +56,7 @@ def on_failure(&block) (@_async_callbacks ||= []) << { :failure => block } end end - + # Registers a callback to be called when the task is complete, # regardless of its status. Yields the status to the block. # @@ -76,7 +76,7 @@ def on_complete(&block) } end end - + private def __send_signal(kind) @_async_status = kind @@ -84,7 +84,7 @@ def __send_signal(kind) cb[kind] end.compact.each {|block| block.call } if @_async_callbacks end - + end end end diff --git a/lib/aws/core/cacheable.rb b/lib/aws/core/cacheable.rb index b0c9a550328..efe0995d3e3 100644 --- a/lib/aws/core/cacheable.rb +++ b/lib/aws/core/cacheable.rb @@ -13,23 +13,23 @@ module AWS module Core - + # @private module Cacheable - + # @private class NoData < StandardError; end - + def self.included base base.extend Naming unless base.respond_to?(:service_ruby_name) end - + # @private protected def local_cache_key raise NotImplementedError end - + # @private protected def cache_key @@ -41,28 +41,28 @@ def cache_key local_cache_key end end - + # @private public def retrieve_attribute attr, &block - + if cache = AWS.response_cache - + if cache.resource_cache.cached?(cache_key, attr.name) return cache.resource_cache.get(cache_key, attr.name) end - + cache.select(*attr.request_types).each do |response| if attributes = attributes_from_response(response) cache.resource_cache.store(cache_key, attributes) return attributes[attr.name] if attributes.key?(attr.name) end end - + end - + response = yield - + if attributes = attributes_from_response(response) if cache = AWS.response_cache cache.resource_cache.store(cache_key, attributes) @@ -72,7 +72,7 @@ def retrieve_attribute attr, &block raise NoData.new("no data in #{response.request_type} response") end end - + end end end diff --git a/lib/aws/core/http/response.rb b/lib/aws/core/http/response.rb index a6797d8bda7..30da991a4a3 100644 --- a/lib/aws/core/http/response.rb +++ b/lib/aws/core/http/response.rb @@ -42,7 +42,7 @@ def network_error? @network_error ? true : false end - # The #network_error attribute was previously #timeout, aliasing + # The #network_error attribute was previously #timeout, aliasing # for backwards compatability alias_method :timeout=, :network_error= diff --git a/lib/aws/core/lazy_error_classes.rb b/lib/aws/core/lazy_error_classes.rb index 1ee3a26b573..b22b65ac393 100644 --- a/lib/aws/core/lazy_error_classes.rb +++ b/lib/aws/core/lazy_error_classes.rb @@ -16,7 +16,7 @@ module AWS module Core - # Provides lazy creation of error classes via {#const_missing}. + # Provides lazy creation of error classes via {#const_missing}. # # Extend this module provides 3 benefits to another module: # @@ -26,7 +26,7 @@ module Core # # Here is an example of how it works: # - # Class Foo + # Class Foo # module Errors # extend AWS::Core::LazyErrorClasses # end @@ -43,7 +43,7 @@ module Core # module LazyErrorClasses - # This grammar parses the defualt AWS XML error format + # This grammar parses the defualt AWS XML error format BASE_ERROR_GRAMMAR = XML::Grammar.customize do element("Error") do ignore @@ -56,12 +56,12 @@ def self.extended base unless base.const_defined?(:GRAMMAR) base.const_set(:GRAMMAR, BASE_ERROR_GRAMMAR) end - + mutex = Mutex.new MetaUtils.extend_method(base, :const_missing_mutex) { mutex } end - + # Defines a new error class. # @param [String,Symbol] constant # @return [nil] @@ -75,7 +75,7 @@ def const_missing constant # # AWS::EC2::Errors.error_class('Non.Existent.Error') # #=> AWS::EC2::Errors::Non::Existent::Error - # + # # @param [String] code An AWS error code. # # @return [Class] Returns the error class defiend by the error code. @@ -83,7 +83,7 @@ def const_missing constant def error_class code module_eval("#{self}::#{code.gsub('.Range','Range').gsub(".","::")}") end - + end end diff --git a/lib/aws/core/options/json_serializer.rb b/lib/aws/core/options/json_serializer.rb index b8be1a709dd..e77703467c0 100644 --- a/lib/aws/core/options/json_serializer.rb +++ b/lib/aws/core/options/json_serializer.rb @@ -19,7 +19,7 @@ module Core module Options # Given a hash of serialization rules, a JSONSerializer can convert - # a hash of request options into a JSON document. The request options + # a hash of request options into a JSON document. The request options # are validated before returning JSON. class JSONSerializer diff --git a/lib/aws/core/page_result.rb b/lib/aws/core/page_result.rb index 9f71a02af79..764ace2d6e1 100644 --- a/lib/aws/core/page_result.rb +++ b/lib/aws/core/page_result.rb @@ -29,7 +29,7 @@ class PageResult < Array # token behaves as a pseudo offset. If +next_token+ is +nil+ then # there are no more results for the collection. attr_reader :next_token - + # @param [Collection] collection The collection that was used to # request this page of results. The collection should respond to # #page and accept a :next_token option. @@ -42,7 +42,7 @@ class PageResult < Array # then this is the last page of results. # # @param [String] next_token (nil) A token that can be passed to the - # + # def initialize collection, items, per_page, next_token @collection = collection @per_page = per_page diff --git a/lib/aws/core/signature/version_3.rb b/lib/aws/core/signature/version_3.rb index 803d32aed38..3e4138478ed 100644 --- a/lib/aws/core/signature/version_3.rb +++ b/lib/aws/core/signature/version_3.rb @@ -28,7 +28,7 @@ def add_authorization! credentials headers["x-amz-date"] ||= (headers["date"] ||= Time.now.httpdate) headers["host"] ||= host - headers["x-amz-security-token"] = credentials.session_token if + headers["x-amz-security-token"] = credentials.session_token if credentials.session_token # compute the authorization @@ -43,7 +43,7 @@ def add_authorization! credentials protected def signature credentials - Signer.sign(credentials.secret_access_key, string_to_sign) + Signer.sign(credentials.secret_access_key, string_to_sign) end def string_to_sign diff --git a/lib/aws/core/uri_escape.rb b/lib/aws/core/uri_escape.rb index 463c0208705..e92c419d7c6 100644 --- a/lib/aws/core/uri_escape.rb +++ b/lib/aws/core/uri_escape.rb @@ -28,7 +28,7 @@ def escape value module_function :escape # @param [String] value - # @return [String] Returns a URI-escaped path without escaping the + # @return [String] Returns a URI-escaped path without escaping the # separators. def escape_path value escaped = "" diff --git a/lib/aws/core/xml/frame_stack.rb b/lib/aws/core/xml/frame_stack.rb index 6d3b4b10f5d..b0ef0848817 100644 --- a/lib/aws/core/xml/frame_stack.rb +++ b/lib/aws/core/xml/frame_stack.rb @@ -36,7 +36,7 @@ def parse xml sax_parse(xml) @frame.value end - + # Increase the frame stack level by one. # @param [String] element_name The name of the xml opening tag. # @param [Hash] attributes A hash of xml element attributes. diff --git a/lib/aws/core/xml/sax_handlers/libxml.rb b/lib/aws/core/xml/sax_handlers/libxml.rb index dc19419fec1..de374d2ae90 100644 --- a/lib/aws/core/xml/sax_handlers/libxml.rb +++ b/lib/aws/core/xml/sax_handlers/libxml.rb @@ -18,7 +18,7 @@ module Core module XML module SaxHandlers class LibXML - + include FrameStack include ::LibXML::XML::SaxParser::Callbacks @@ -29,7 +29,7 @@ def sax_parse xml end def on_start_element_ns element_name, attributes, *ignore - start_element(element_name, attributes) + start_element(element_name, attributes) end def on_end_element_ns *ignore diff --git a/lib/aws/core/xml/sax_handlers/nokogiri.rb b/lib/aws/core/xml/sax_handlers/nokogiri.rb index e7f535fc481..216a2f2f49a 100644 --- a/lib/aws/core/xml/sax_handlers/nokogiri.rb +++ b/lib/aws/core/xml/sax_handlers/nokogiri.rb @@ -18,7 +18,7 @@ module Core module XML module SaxHandlers class Nokogiri - + include FrameStack def sax_parse xml @@ -32,11 +32,11 @@ def error(*args); end def start_element_namespace element_name, attributes = [], *ignore - attributes = attributes.map.inject({}) do |hash,attr| + attributes = attributes.map.inject({}) do |hash,attr| hash.merge(attr.localname => attr.value) end - start_element(element_name, attributes) + start_element(element_name, attributes) end diff --git a/lib/aws/core/xml/sax_handlers/rexml.rb b/lib/aws/core/xml/sax_handlers/rexml.rb index 97189769b3d..7e833efd610 100644 --- a/lib/aws/core/xml/sax_handlers/rexml.rb +++ b/lib/aws/core/xml/sax_handlers/rexml.rb @@ -19,23 +19,23 @@ module Core module XML module SaxHandlers class REXML - + include FrameStack include ::REXML::StreamListener - + def sax_parse xml source = ::REXML::Source.new(xml) ::REXML::Parsers::StreamParser.new(source, self).parse end - + def tag_start name, attrs start_element(name, attrs) end - + def tag_end name end_element end - + end end end diff --git a/lib/aws/core/xml/stub.rb b/lib/aws/core/xml/stub.rb index 12fc6b362e5..5631bc26db9 100644 --- a/lib/aws/core/xml/stub.rb +++ b/lib/aws/core/xml/stub.rb @@ -37,9 +37,9 @@ def initialize rules attr_reader :rules # Returns a hash with stubbed values as if it had parsed - # an empty xml document. - # @return [Hash] - def simulate + # an empty xml document. + # @return [Hash] + def simulate if rules[:children] data = stub_data_for(rules) apply_empty_indexes(rules, data) @@ -50,9 +50,9 @@ def simulate end # Returns a hash with stubbed values as if it had parsed - # an empty xml document. + # an empty xml document. # @param [Hash] rules An XML::Parser rule set. - # @return [Hash] + # @return [Hash] def self.simulate rules stub = Stub.new(rules) stub.simulate @@ -74,7 +74,7 @@ def stub_data_for rules, data = {} data = data[wrapper] end - ruby_name = child_rules[:rename] || + ruby_name = child_rules[:rename] || Inflection.ruby_name(name.to_s).to_sym if child_rules[:list] @@ -85,7 +85,7 @@ def stub_data_for rules, data = {} data[ruby_name] = stub_data_for(child_rules) else data[ruby_name] = case child_rules[:type] - when :integer then 0 + when :integer then 0 when :float then 0.0 when :time then Time.now when :datetime then Date.parse(Time.now.to_s) @@ -105,9 +105,9 @@ def stub_data_for rules, data = {} protected def apply_empty_indexes rules, data - + return unless rules[:children] - + rules[:children].each_pair do |name,child_rules| if index = child_rules[:index] data[index[:name]] = {} diff --git a/lib/aws/dynamo_db/batch_write.rb b/lib/aws/dynamo_db/batch_write.rb index bbb97d40180..516fa328423 100644 --- a/lib/aws/dynamo_db/batch_write.rb +++ b/lib/aws/dynamo_db/batch_write.rb @@ -43,7 +43,7 @@ def initialize options = {} # @param [Table,String] table A {Table} object or table name string. # # @param [Array] items A list of item attributes to put. - # The hash must contain the table hash key element and range key + # The hash must contain the table hash key element and range key # element (if one is defined). # # @return [nil] @@ -67,13 +67,13 @@ def put table, items # # @param [Table,String] table A {Table} object or table name string. # - # @param [Array,Array] items A list of item keys to + # @param [Array,Array] items A list of item keys to # delete. For tables without a range key, items should be an array # of hash key strings. # # batch.delete('table-name', ['hk1', 'hk2', 'hk3']) # - # For tables with a range key, items should be an array of + # For tables with a range key, items should be an array of # hash key and range key pairs. # # batch.delete('table-name', [['hk1', 'rk1'], ['hk1', 'rk2']]) @@ -108,14 +108,14 @@ def delete table, items # # batch.write('table-name', :delete => ['hk1', 'hk2', 'hk3']) # - # For tables with a range key, items should be an array of + # For tables with a range key, items should be an array of # hash key and range key pairs. # # batch.write('table-name', :delete => [['hk1', 'rk1'], ['hk1', 'rk2']]) # def write table, options = {} - items = table_items(table) + items = table_items(table) if put = options[:put] put.each do |attributes| @@ -198,7 +198,7 @@ def convert_unprocessed_items items item = request.values.first - request_items[table] << + request_items[table] << case request.keys.first when 'PutRequest' then convert_put_item(item['Item']) when 'DeleteRequest' then convert_delete_item(item['Key']) @@ -213,13 +213,13 @@ def convert_unprocessed_items items end def convert_put_item item - + attributes = {} item.each_pair do |name,value| attributes[name] = str2sym(value) end - - { :put_request => { :item => attributes }} + + { :put_request => { :item => attributes }} end @@ -227,7 +227,7 @@ def convert_delete_item item key = {} key[:hash_key_element] = str2sym(item['HashKeyElement']) - key[:range_key_element] = str2sym(item['RangeKeyElement']) if + key[:range_key_element] = str2sym(item['RangeKeyElement']) if item['RangeKeyElement'] { :delete_request => { :key => key}} @@ -242,7 +242,7 @@ def str2sym key_desc when "N" then { :n => value } when "SS" then { :ss => value } when "NS" then { :ns => value } - else + else raise "unhandled key type: #{type.inspect}" end end diff --git a/lib/aws/dynamo_db/item.rb b/lib/aws/dynamo_db/item.rb index 270d9828849..10721ec6562 100644 --- a/lib/aws/dynamo_db/item.rb +++ b/lib/aws/dynamo_db/item.rb @@ -18,16 +18,16 @@ class DynamoDB # complex primary key (according to the table schema) and consists # of a collection of attributes. Attributes are name/value pairs # where the value may be a string, number, string set, or number - # set. + # set. # # Getting an item by hash key value: # - # item = table.items['hash-key-value'] + # item = table.items['hash-key-value'] # # Getting an item from a table with both hash and range keys: # # item = table.items['hash-key','range-key'] - # + # class Item < Core::Resource extend Types diff --git a/lib/aws/ec2.rb b/lib/aws/ec2.rb index 047084d2185..7dc457c1a56 100644 --- a/lib/aws/ec2.rb +++ b/lib/aws/ec2.rb @@ -120,7 +120,7 @@ module AWS # instance.ip_address # 1.1.1.1 # # When you are done with an elastic IP address you should release it. - # In the following example we release all elastic IP addresses that are + # In the following example we release all elastic IP addresses that are # not currently associated with an instance: # # ec2.select{|ip| !ip.associated? }.each(&:release) @@ -368,7 +368,7 @@ def snapshots SnapshotCollection.new(:config => config) end - # @return [VPCCollection] A collection representing + # @return [VPCCollection] A collection representing # all VPCs in your account. def vpcs VPCCollection.new(:config => config) diff --git a/lib/aws/ec2/attachment.rb b/lib/aws/ec2/attachment.rb index 8dbceaed734..d7b64b7ed3b 100644 --- a/lib/aws/ec2/attachment.rb +++ b/lib/aws/ec2/attachment.rb @@ -54,24 +54,24 @@ def initialize volume, instance, device, options = {} # * +:attached+ # * +:detaching+ # * +:detached+ - # @return [Symbol] Returns the attachment status. + # @return [Symbol] Returns the attachment status. attribute :status, :to_sym => true # @overload attach_time # @return [Time] Returns the time at which this attachment was created. attribute :attach_time - # @overload delete_on_termination? - # @return [Boolean] Returns +true+ if the volume will be deleted + # @overload delete_on_termination? + # @return [Boolean] Returns +true+ if the volume will be deleted # on instance termination. attribute :delete_on_termination, :boolean => true - + populates_from(:describe_volumes) do |resp| find_attachment(resp) end populates_from(:attach_volume, :detach_volume) do |resp| - if + if resp.volume_id == volume.id and resp.instance_id == instance.id and resp.device == device diff --git a/lib/aws/ec2/attachment_collection.rb b/lib/aws/ec2/attachment_collection.rb index bde81dc2535..939059b3adf 100644 --- a/lib/aws/ec2/attachment_collection.rb +++ b/lib/aws/ec2/attachment_collection.rb @@ -39,7 +39,7 @@ def each &block instance = Instance.new(item.instance_id, :config => config) - attachment = Attachment.new(self.volume, instance, item.device, + attachment = Attachment.new(self.volume, instance, item.device, :config => config) yield(attachment) diff --git a/lib/aws/ec2/availability_zone.rb b/lib/aws/ec2/availability_zone.rb index 3f7932311be..a5a6cf85941 100644 --- a/lib/aws/ec2/availability_zone.rb +++ b/lib/aws/ec2/availability_zone.rb @@ -21,10 +21,10 @@ class EC2 # @attr_reader [String,nil] region_name Returns the region name # of the availability zone. # - # @attr_reader [Symbol] state Returns the state of the availability + # @attr_reader [Symbol] state Returns the state of the availability # zone, e.g. +:available+. # - # @attr_reader [Array] messages Returns a list of messages about the + # @attr_reader [Array] messages Returns a list of messages about the # Availability Zone. # class AvailabilityZone < Resource diff --git a/lib/aws/ec2/customer_gateway.rb b/lib/aws/ec2/customer_gateway.rb index 93048656814..50def5997ff 100644 --- a/lib/aws/ec2/customer_gateway.rb +++ b/lib/aws/ec2/customer_gateway.rb @@ -14,16 +14,16 @@ module AWS class EC2 - # @attr_reader [Symbol] state Returns the gateway state (e.g. + # @attr_reader [Symbol] state Returns the gateway state (e.g. # :pending, :available, :deleting, :deleted) # - # @attr_reader [String] type The type of VPN connection the customer + # @attr_reader [String] type The type of VPN connection the customer # gateway supports (e.g. 'ipsec.1'). # - # @attr_reader [String] ip_address The Internet-routable IP address of + # @attr_reader [String] ip_address The Internet-routable IP address of # the customer gateway's outside interface. # - # @attr_reader [Integer] bgp_asn The customer gateway's Border Gateway + # @attr_reader [Integer] bgp_asn The customer gateway's Border Gateway # Protocol (BGP) Autonomous System Number (ASN). # class CustomerGateway < Resource @@ -40,7 +40,7 @@ def initialize customer_gateway_id, options = {} attr_reader :customer_gateway_id alias_method :id, :customer_gateway_id - + attribute :state, :to_sym => true attribute :vpn_type, :static => true @@ -54,7 +54,7 @@ def initialize customer_gateway_id, options = {} end populates_from(:describe_customer_gateways) do |resp| - resp.customer_gateway_set.find do |gateway| + resp.customer_gateway_set.find do |gateway| gateway.customer_gateway_id == customer_gateway_id end end diff --git a/lib/aws/ec2/customer_gateway_collection.rb b/lib/aws/ec2/customer_gateway_collection.rb index 153371a2b9e..4b6fa5d713b 100644 --- a/lib/aws/ec2/customer_gateway_collection.rb +++ b/lib/aws/ec2/customer_gateway_collection.rb @@ -19,15 +19,15 @@ class CustomerGatewayCollection < Collection include TaggedCollection include Core::Collection::Simple - # @param [Integer] bgp_asn The customer gateway's Border Gateway + # @param [Integer] bgp_asn The customer gateway's Border Gateway # Protocol (BGP) Autonomous System Number (ASN). # - # @param [String] ip_address The Internet-routable IP address for the + # @param [String] ip_address The Internet-routable IP address for the # customer gateway's outside interface. The address must be static. # # @param [Hash] options - # - # @option options [String] :vpn_type ('ipsec.1') The type of VPN + # + # @option options [String] :vpn_type ('ipsec.1') The type of VPN # connection this customer gateway supports. # # @return [CustomerGateway] @@ -41,7 +41,7 @@ def create bgp_asn, ip_address, options = {} resp = client.create_customer_gateway(client_opts) - CustomerGateway.new_from(:create_customer_gateway, + CustomerGateway.new_from(:create_customer_gateway, resp.customer_gateway, resp.customer_gateway.customer_gateway_id, :config => config) @@ -60,7 +60,7 @@ def _each_item options = {}, &block response = filtered_request(:describe_customer_gateways, options, &block) response.customer_gateway_set.each do |g| - gateway = CustomerGateway.new_from(:describe_customer_gateways, g, + gateway = CustomerGateway.new_from(:describe_customer_gateways, g, g.customer_gateway_id, :config => config) yield(gateway) diff --git a/lib/aws/ec2/dhcp_options.rb b/lib/aws/ec2/dhcp_options.rb index e3442881651..d1853309e43 100644 --- a/lib/aws/ec2/dhcp_options.rb +++ b/lib/aws/ec2/dhcp_options.rb @@ -32,13 +32,13 @@ def initialize dhcp_options_id, options = {} attribute :dhcp_configuration_set, :static => true protected :dhcp_configuration_set - + populates_from(:create_dhcp_options) do |resp| resp.dhcp_options if resp.dhcp_options.dhcp_options_id == id end populates_from(:describe_dhcp_options) do |resp| - resp.dhcp_options_set.find do |dhcp_options| + resp.dhcp_options_set.find do |dhcp_options| dhcp_options.dhcp_options_id == dhcp_options_id end end @@ -78,7 +78,7 @@ def delete client.delete_dhcp_options(client_opts) nil end - + # @return [VPCCollection] Returns a collection that represents # all VPCs currently using this dhcp options. def vpcs diff --git a/lib/aws/ec2/dhcp_options_collection.rb b/lib/aws/ec2/dhcp_options_collection.rb index d82cac5c8ad..be2c83a9467 100644 --- a/lib/aws/ec2/dhcp_options_collection.rb +++ b/lib/aws/ec2/dhcp_options_collection.rb @@ -25,20 +25,20 @@ class DHCPOptionsCollection < Collection # choice (e.g., example.com). # # @option options [Array] :domain_name_servers - # The IP addresses of domain name servers. You can specify up to + # The IP addresses of domain name servers. You can specify up to # four addresses. # - # @option options [Array] :ntp_servers - # The IP addresses of Network Time Protocol (NTP) servers. You can + # @option options [Array] :ntp_servers + # The IP addresses of Network Time Protocol (NTP) servers. You can # specify up to four addresses. # - # @option options [Array] :netbios_name_servers - # The IP addresses of NetBIOS name servers. You can specify up to + # @option options [Array] :netbios_name_servers + # The IP addresses of NetBIOS name servers. You can specify up to # four addresses. # - # @option options [String] :netbios_node_type Value indicating the - # NetBIOS node type (1, 2, 4, or 8). For more information about the - # values, go to RFC 2132. We recommend you only use 2 at this time + # @option options [String] :netbios_node_type Value indicating the + # NetBIOS node type (1, 2, 4, or 8). For more information about the + # values, go to RFC 2132. We recommend you only use 2 at this time # (broadcast and multicast are currently not supported). # def create options = {} @@ -55,7 +55,7 @@ def create options = {} resp = client.create_dhcp_options(client_opts) - DHCPOptions.new_from(:create_dhcp_options, + DHCPOptions.new_from(:create_dhcp_options, resp.dhcp_options, resp.dhcp_options.dhcp_options_id, :config => config) @@ -74,7 +74,7 @@ def _each_item options = {}, &block response = filtered_request(:describe_dhcp_options, options, &block) response.dhcp_options_set.each do |opts| - options = DHCPOptions.new_from(:describe_dhcp_options, opts, + options = DHCPOptions.new_from(:describe_dhcp_options, opts, opts.dhcp_options_id, :config => config) yield(options) diff --git a/lib/aws/ec2/elastic_ip.rb b/lib/aws/ec2/elastic_ip.rb index 9db57b67ce1..31c2efa6919 100644 --- a/lib/aws/ec2/elastic_ip.rb +++ b/lib/aws/ec2/elastic_ip.rb @@ -14,23 +14,23 @@ module AWS class EC2 - # @attr_reader [String,nil] instance_id Returns the instance id if + # @attr_reader [String,nil] instance_id Returns the instance id if # assigned to an EC2 instance, nil otherwise. # # @attr_reader [String,nil] allocation_id - # The ID representing the allocation of the address for use with Amazon + # The ID representing the allocation of the address for use with Amazon # VPC. # - # @attr_reader [String] domain Indicates whether this elastic ip address + # @attr_reader [String] domain Indicates whether this elastic ip address # is for EC2 instances ('standard') or VPC instances ('vpc'). # - # @attr_reader [String,nil] association_id The ID of the association + # @attr_reader [String,nil] association_id The ID of the association # between this elastic ip address and an EC2 VPC instance (VPC only). # - # @attr_reader [String,nil] network_interface_id The ID of the network + # @attr_reader [String,nil] network_interface_id The ID of the network # interface (VPC only). # - # @attr_reader [String,nil] network_interface_owner_id + # @attr_reader [String,nil] network_interface_owner_id # The ID of the AWS account that owns the network interface (VPC only). # class ElasticIp < Resource @@ -95,7 +95,7 @@ def network_interface # Releases the elastic IP address. # - # (For non-VPC elastic ips) Releasing an IP address automatically + # (For non-VPC elastic ips) Releasing an IP address automatically # disassociates it from any instance it's associated with. # # @return [nil] @@ -122,7 +122,7 @@ def delete # @param [Hash] options # # @option options [String,Instance] :instance The id of an instance - # or an {Instance} object. + # or an {Instance} object. # # @option options [String,NetworkInterface] :network_interface The id # of a network interface or a {NetworkInterface} object. diff --git a/lib/aws/ec2/elastic_ip_collection.rb b/lib/aws/ec2/elastic_ip_collection.rb index a0c21a97510..5a6436f664b 100644 --- a/lib/aws/ec2/elastic_ip_collection.rb +++ b/lib/aws/ec2/elastic_ip_collection.rb @@ -17,7 +17,7 @@ class ElasticIpCollection < Collection # @param [Hash] options # - # @option options [Boolean] :vpc (false) When true, the elastic ip + # @option options [Boolean] :vpc (false) When true, the elastic ip # address will be allocated to your VPC. # # @return [ElasticIp] @@ -44,11 +44,11 @@ def [] public_ip # Specify one or more criteria to filter elastic IP addresses by. # A subsequent call to #each will limit the resutls returned # by provided filters. - # + # # * Chain multiple calls of #filter together to AND multiple conditions # together. # - # * Supply multiple values to a singler #filter call to OR those + # * Supply multiple values to a singler #filter call to OR those # value conditions together. # # * '*' matches one or more characters and '?' matches any one @@ -56,18 +56,18 @@ def [] public_ip # # === Valid Filters # - # * domain - Whether the address is a EC2 address, or a VPC address. + # * domain - Whether the address is a EC2 address, or a VPC address. # Valid values include 'standard' and 'vpc' # * instance-id - Instance the address is associated with (if any). # * public-ip - The Elastic IP address. - # * allocation-id - Allocation ID for the address. For VPC addresses + # * allocation-id - Allocation ID for the address. For VPC addresses # only. - # * association-id - Association ID for the address. For VPC addresses + # * association-id - Association ID for the address. For VPC addresses # only. # # @return [ElasticIpCollection] A new collection that represents # a subset of the elastic IP addresses associated with this account. - + # Yields once for each elastic IP address. # # @yield [elastic_ip] diff --git a/lib/aws/ec2/export_task.rb b/lib/aws/ec2/export_task.rb index 1f7b601de68..439cd0117e2 100644 --- a/lib/aws/ec2/export_task.rb +++ b/lib/aws/ec2/export_task.rb @@ -20,15 +20,15 @@ class EC2 # @attr_reader [Symbol] state State of the conversion task. # Valid values :active, :cancelling, :cancelled and :completed. # - # @attr_reader [String] status_message Status message related to the + # @attr_reader [String] status_message Status message related to the # export task. # # @attr_reader [String] instance_id ID of instance being exported. # - # @attr_reader [String] target_environment The target virtualization + # @attr_reader [String] target_environment The target virtualization # environment. # - # @attr_reader [String] disk_image_format The format for the exported + # @attr_reader [String] disk_image_format The format for the exported # image. # # @attr_reader [String] container_format The container format used to @@ -59,7 +59,7 @@ def initialize export_task_id, options = {} attribute :status_message - attribute :instance_id, + attribute :instance_id, :from => [:instance_export, :instance_id], :static => true diff --git a/lib/aws/ec2/export_task_collection.rb b/lib/aws/ec2/export_task_collection.rb index 40f433d4b9a..6b12d81fcfc 100644 --- a/lib/aws/ec2/export_task_collection.rb +++ b/lib/aws/ec2/export_task_collection.rb @@ -33,7 +33,7 @@ class EC2 # task = ec2.instances['i-12345678'].export_to_s3('bucket-name') # # See {Instance#export_to_s3} for more options. - # + # class ExportTaskCollection < Collection include Core::Collection::Simple @@ -50,11 +50,11 @@ def [] export_task_id def _each_item options = {}, &block resp = filtered_request(:describe_export_tasks, options, &block) resp.data[:export_task_set].each do |details| - + task = ExportTask.new_from( - :describe_export_tasks, + :describe_export_tasks, details, - details[:export_task_id], + details[:export_task_id], :config => config) yield(task) diff --git a/lib/aws/ec2/image.rb b/lib/aws/ec2/image.rb index d241b9fe6f8..785a52f2515 100644 --- a/lib/aws/ec2/image.rb +++ b/lib/aws/ec2/image.rb @@ -35,7 +35,7 @@ class EC2 # @attr_reader [String] owner_alias The AWS account alias (e.g., # +"amazon"+) or AWS account ID that owns the AMI. # - # @attr_reader [Symbol] architecture The architecture of the + # @attr_reader [Symbol] architecture The architecture of the # image (e.g. +:i386+). # # @attr_reader [Symbol] type The type of image. Valid values are: @@ -73,7 +73,7 @@ class EC2 # * +:paravirtual+ # * +:hvm+ # - # @attr_reader [Symbol] hypervisor The image's hypervisor type. + # @attr_reader [Symbol] hypervisor The image's hypervisor type. # Possible values are: # * +:ovm+ # * +:xen+ @@ -183,7 +183,7 @@ def deregister alias_method :delete, :deregister - # Runs a single instance of this image. + # Runs a single instance of this image. # # @param [Hash] options # @option (see InstanceCollection#create) @@ -238,10 +238,10 @@ def ramdisk # image.add_product_codes 'ABCXYZ', 'MNOPQR' # # You can also pass an array of product codes: - # + # # image.add_product_codes ['ABCXYZ', 'MNOPQR'] # - # @param [Array] product_codes + # @param [Array] product_codes # # @return [nil] # diff --git a/lib/aws/ec2/instance_collection.rb b/lib/aws/ec2/instance_collection.rb index 0ff9cbe7251..30028e25722 100644 --- a/lib/aws/ec2/instance_collection.rb +++ b/lib/aws/ec2/instance_collection.rb @@ -192,13 +192,13 @@ class InstanceCollection < Collection # valid for instances launched outside a VPC (e.g. those # launched without the :subnet option). # - # @option options [Boolean] :ebs_optimized (false) EBS-Optimized instances - # enable Amazon EC2 instances to fully utilize the IOPS provisioned on - # an EBS volume. EBS-optimized instances deliver dedicated throughput + # @option options [Boolean] :ebs_optimized (false) EBS-Optimized instances + # enable Amazon EC2 instances to fully utilize the IOPS provisioned on + # an EBS volume. EBS-optimized instances deliver dedicated throughput # between Amazon EC2 and Amazon EBS, with options between 500 Mbps and - # 1000 Mbps depending on the instance type used. When attached to - # EBS-Optimized instances, Provisioned IOPS volumes are designed - # to deliver within 10% of their provisioned performance 99.9% of the time. + # 1000 Mbps depending on the instance type used. When attached to + # EBS-Optimized instances, Provisioned IOPS volumes are designed + # to deliver within 10% of their provisioned performance 99.9% of the time. # *NOTE:* EBS Optimized instances incur an additional service charge. This # optional is only valid for certain instance types. # diff --git a/lib/aws/ec2/internet_gateway.rb b/lib/aws/ec2/internet_gateway.rb index 714e08fb416..d0de8bd591e 100644 --- a/lib/aws/ec2/internet_gateway.rb +++ b/lib/aws/ec2/internet_gateway.rb @@ -29,7 +29,7 @@ def initialize internet_gateway_id, options = {} attr_reader :internet_gateway_id alias_method :id, :internet_gateway_id - + attribute :vpc_id attribute :attachment_set @@ -37,7 +37,7 @@ def initialize internet_gateway_id, options = {} protected :attachment_set populates_from(:describe_internet_gateways) do |resp| - resp.internet_gateway_set.find do |gateway| + resp.internet_gateway_set.find do |gateway| gateway.internet_gateway_id == internet_gateway_id end end @@ -50,7 +50,7 @@ def attachments # @return [VPC,nil] Returns the currently attached VPC, or nil # if this gateway has not been attached. def vpc - if attachment = attachments.first + if attachment = attachments.first attachment.vpc end end @@ -58,10 +58,10 @@ def vpc # Attaches this internet gateway to the given VPC. If this # gateway is already attached to a different VPC, it will # be detached from that one first. If you pass nil, then - # this internet gateway will + # this internet gateway will # # internet_gateway.vpc = 'vpc-123' - # + # # @param [VPC,String] vpc A {VPC} object or a vpc id string. # def vpc= vpc diff --git a/lib/aws/ec2/internet_gateway_collection.rb b/lib/aws/ec2/internet_gateway_collection.rb index a4820ecc1ee..d6f3aac9c22 100644 --- a/lib/aws/ec2/internet_gateway_collection.rb +++ b/lib/aws/ec2/internet_gateway_collection.rb @@ -19,14 +19,14 @@ class InternetGatewayCollection < Collection include TaggedCollection include Core::Collection::Simple - # Creates a new Internet gateway in your AWS account. After creating + # Creates a new Internet gateway in your AWS account. After creating # the gateway you can attach it to a VPC. # # @return [InternetGateway] # def create response = client.create_internet_gateway - self[response.internet_gateway.internet_gateway_id] + self[response.internet_gateway.internet_gateway_id] end # @param [String] internet_gateway_id @@ -41,7 +41,7 @@ def _each_item options = {}, &block response = filtered_request(:describe_internet_gateways, options, &block) response.internet_gateway_set.each do |g| - gateway = InternetGateway.new_from(:describe_internet_gateways, g, + gateway = InternetGateway.new_from(:describe_internet_gateways, g, g.internet_gateway_id, :config => config) yield(gateway) diff --git a/lib/aws/ec2/key_pair.rb b/lib/aws/ec2/key_pair.rb index fdc4d5b8c1c..43d9bcd1957 100644 --- a/lib/aws/ec2/key_pair.rb +++ b/lib/aws/ec2/key_pair.rb @@ -15,7 +15,7 @@ module AWS class EC2 # Represents an EC2 key pair. - # @attr_reader [String] fingerprint A SHA-1 digest of the DER encoded + # @attr_reader [String] fingerprint A SHA-1 digest of the DER encoded # private key class KeyPair < Resource @@ -48,7 +48,7 @@ def exists? # Returns the private key. Raises an exception if called # against an existing key. You can only get the private key - # at the time of creation. + # at the time of creation. # # @see KeyPairCollection#import # @note Only call this method on newly created keys. @@ -63,7 +63,7 @@ def private_key # Deletes this key pair from EC2. # @return [true] def delete - client.delete_key_pair(:key_name => name) + client.delete_key_pair(:key_name => name) true end diff --git a/lib/aws/ec2/key_pair_collection.rb b/lib/aws/ec2/key_pair_collection.rb index 5538ad797aa..1058c178344 100644 --- a/lib/aws/ec2/key_pair_collection.rb +++ b/lib/aws/ec2/key_pair_collection.rb @@ -27,28 +27,28 @@ def create key_name create_or_import(:create_key_pair, :key_name => key_name) end - # Imports the public key from an RSA key pair that you created with - # a third-party tool. Compare this with {#create}, in which EC2 - # creates the key pair and gives the keys to you (EC2 keeps a copy - # of the public key). With ImportKeyPair, you create the key pair - # and give EC2 just the public key. The private key is never + # Imports the public key from an RSA key pair that you created with + # a third-party tool. Compare this with {#create}, in which EC2 + # creates the key pair and gives the keys to you (EC2 keeps a copy + # of the public key). With ImportKeyPair, you create the key pair + # and give EC2 just the public key. The private key is never # transferred between you and EC2. - # + # # === Supported formats: # - # * OpenSSH public key format (e.g., the format in - # ~/.ssh/authorized_keys) - # * Base64 encoded DER format + # * OpenSSH public key format (e.g., the format in + # ~/.ssh/authorized_keys) + # * Base64 encoded DER format # * SSH public key file format as specified in RFC4716 # - # DSA keys are *not* supported. Make sure your key generator is + # DSA keys are *not* supported. Make sure your key generator is # set up to create RSA keys. Supported lengths: 1024, 2048, and 4096. # # @param [String] key_name A name for this key pair. # @param [String] public_key The RSA public key. # @return [KeyPair] Returns a new key pair. def import key_name, public_key - create_or_import(:import_key_pair, + create_or_import(:import_key_pair, :key_name => key_name, :public_key_material => Base64.encode64(public_key.to_s)) end diff --git a/lib/aws/ec2/network_acl.rb b/lib/aws/ec2/network_acl.rb index 490699539c9..25994c88dd5 100644 --- a/lib/aws/ec2/network_acl.rb +++ b/lib/aws/ec2/network_acl.rb @@ -21,7 +21,7 @@ class EC2 # # @attr_reader [String] vpc_id # - # @attr_reader [Boolean] default Returns true if this is the default + # @attr_reader [Boolean] default Returns true if this is the default # network ACL. # class NetworkACL < Resource @@ -65,27 +65,27 @@ def vpc VPC.new(vpc_id, :config => config) end - # @return [Array] Returns an array of subnets ({Subnet}) + # @return [Array] Returns an array of subnets ({Subnet}) # that currently use this network ACL. def subnets associations.map(&:subnet) end - # @return [Array] Returns an array of + # @return [Array] Returns an array of # {NetworkACL::Association} objects (association to subnets). def associations association_set.map do |assoc| - subnet = Subnet.new(assoc.subnet_id, - :vpc_id => vpc_id, + subnet = Subnet.new(assoc.subnet_id, + :vpc_id => vpc_id, :config => config) Association.new(assoc.network_acl_association_id, self, subnet) - + end end - # @return [Array] Returns an array of + # @return [Array] Returns an array of # all entries for this network ACL. def entries entry_set.map do |entry_details| @@ -97,34 +97,34 @@ def entries # # @param [Hash] options # - # @option options [required,Integer] :rule_number Rule number to - # assign to the entry (e.g., 100). ACL entries are processed in + # @option options [required,Integer] :rule_number Rule number to + # assign to the entry (e.g., 100). ACL entries are processed in # ascending order by rule number. # - # @option options [required,:allow,:deny] :action Whether to + # @option options [required,:allow,:deny] :action Whether to # allow or deny traffic that matches the rule. # - # @option options [required,Integer] :protocol IP protocol the rule - # applies to. You can use -1 to mean all protocols. You can see a - # list of # supported protocol numbers here: + # @option options [required,Integer] :protocol IP protocol the rule + # applies to. You can use -1 to mean all protocols. You can see a + # list of # supported protocol numbers here: # http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml # - # @option options [required,String] :cidr_block The CIDR range to + # @option options [required,String] :cidr_block The CIDR range to # allow or deny, in CIDR notation (e.g., 172.16.0.0/24). # - # @option options [Boolean] :egress (false) - # Whether this rule applies to egress traffic from the subnet (true) + # @option options [Boolean] :egress (false) + # Whether this rule applies to egress traffic from the subnet (true) # or ingress traffic to the subnet (false). # # @option options [Range] :port_range A numeric range - # of ports. Required if specifying TCP (6) or UDP (17) for the + # of ports. Required if specifying TCP (6) or UDP (17) for the # :protocol. # - # @option options [Integer] :icmp_code For the ICMP protocol, the - # ICMP code. You can use -1 to specify all ICMP codes for the given + # @option options [Integer] :icmp_code For the ICMP protocol, the + # ICMP code. You can use -1 to specify all ICMP codes for the given # ICMP type. # - # @option options [Integer] :icmp_type For the ICMP protocol, + # @option options [Integer] :icmp_type For the ICMP protocol, # the ICMP type. You can use -1 to specify all ICMP types. # # @return [nil] @@ -138,34 +138,34 @@ def create_entry options = {} # # @param [Hash] options # - # @option options [required,Integer] :rule_number Rule number to - # assign to the entry (e.g., 100). ACL entries are processed in + # @option options [required,Integer] :rule_number Rule number to + # assign to the entry (e.g., 100). ACL entries are processed in # ascending order by rule number. # - # @option options [required,:allow,:deny] :action Whether to + # @option options [required,:allow,:deny] :action Whether to # allow or deny traffic that matches the rule. # - # @option options [required,Integer] :protocol IP protocol the rule - # applies to. You can use -1 to mean all protocols. You can see a - # list of # supported protocol numbers here: + # @option options [required,Integer] :protocol IP protocol the rule + # applies to. You can use -1 to mean all protocols. You can see a + # list of # supported protocol numbers here: # http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml # - # @option options [required,String] :cidr_block The CIDR range to + # @option options [required,String] :cidr_block The CIDR range to # allow or deny, in CIDR notation (e.g., 172.16.0.0/24). # - # @option options [Boolean] :egress (false) - # Whether this rule applies to egress traffic from the subnet (true) + # @option options [Boolean] :egress (false) + # Whether this rule applies to egress traffic from the subnet (true) # or ingress traffic to the subnet (false). # # @option options [Range] :port_range A numeric range - # of ports. Required if specifying TCP (6) or UDP (17) for the + # of ports. Required if specifying TCP (6) or UDP (17) for the # :protocol. # - # @option options [Integer] :icmp_code For the ICMP protocol, the - # ICMP code. You can use -1 to specify all ICMP codes for the given + # @option options [Integer] :icmp_code For the ICMP protocol, the + # ICMP code. You can use -1 to specify all ICMP codes for the given # ICMP type. # - # @option options [Integer] :icmp_type For the ICMP protocol, + # @option options [Integer] :icmp_type For the ICMP protocol, # the ICMP type. You can use -1 to specify all ICMP types. # # @return [nil] @@ -178,7 +178,7 @@ def replace_entry options = {} # Deletes an entry from this network ACL. To delete an entry # you need to know its rule number and if it is an egress or ingress # rule. - # + # # # delete ingress rule 10 # network_acl.delete_entry :egress, 10 # @@ -219,7 +219,7 @@ def delete end protected - + def entry_options options unless [true,false].include?(options[:egress]) diff --git a/lib/aws/ec2/network_acl/association.rb b/lib/aws/ec2/network_acl/association.rb index 3d7a6a01b8f..87bf57f91bc 100644 --- a/lib/aws/ec2/network_acl/association.rb +++ b/lib/aws/ec2/network_acl/association.rb @@ -24,7 +24,7 @@ def initialize association_id, network_acl, subnet @subnet = subnet end - # @return [String] An identifier representing the association + # @return [String] An identifier representing the association # between the network ACL and subnet. attr_reader :association_id @@ -34,7 +34,7 @@ def initialize association_id, network_acl, subnet # @return [Subnet] attr_reader :subnet - # Replaces the network acl in the current association with a + # Replaces the network acl in the current association with a # different one (a new network acl is assigned to the subnet). # # @param [NetworkACL,String] network_acl A {NetworkACL} object or diff --git a/lib/aws/ec2/network_acl/entry.rb b/lib/aws/ec2/network_acl/entry.rb index 7350667c283..40af535ac14 100644 --- a/lib/aws/ec2/network_acl/entry.rb +++ b/lib/aws/ec2/network_acl/entry.rb @@ -14,7 +14,7 @@ module AWS class EC2 class NetworkACL < Resource - + # Represents a single entry (rule) for an EC2 network ACL. class Entry @@ -41,36 +41,36 @@ def initialize network_acl, details # @return [Integer] attr_reader :rule_number - # @return [Integer] Returns the protocol number. A value of -1 - # means all protocols. See - # http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml + # @return [Integer] Returns the protocol number. A value of -1 + # means all protocols. See + # http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml # for a list of protocol numbers to names. attr_reader :protocol - # @return [:allow,:deny] Whether to allow or deny the traffic that + # @return [:allow,:deny] Whether to allow or deny the traffic that # matches the rule. attr_reader :action - # @return [Boolean] Indicate the rule is an egress rule (rule is + # @return [Boolean] Indicate the rule is an egress rule (rule is # applied to traffic leaving the subnet). attr_reader :egress - # @return [Boolean] Indicate the rule is an ingress rule (rule is + # @return [Boolean] Indicate the rule is an ingress rule (rule is # applied to traffic entering the subnet). attr_reader :ingress # @return [String] The network range to allow or deny, in CIDR notation. attr_reader :cidr_block - # @return [nil,Range] For the TCP or UDP protocols, the range + # @return [nil,Range] For the TCP or UDP protocols, the range # of ports the rule applies to. attr_reader :port_range - # @return [nil,Integer] A value of -1 means all codes for the given + # @return [nil,Integer] A value of -1 means all codes for the given # ICMP type. Returns nil unless the protocol is ICMP. attr_reader :icmp_code - # @return [nil,Integer] A value of -1 means all codes for the given + # @return [nil,Integer] A value of -1 means all codes for the given # ICMP type. Returns nil unless the protocol is ICMP. attr_reader :icmp_type @@ -85,7 +85,7 @@ def allow? def deny? @action == :deny end - + # @return [Boolean] Returns true if the rule is applied to traffic # entering the subnet. def ingress? @@ -102,30 +102,30 @@ def egress? # # @param [Hash] options # - # @option options [required,:allow,:deny] :rule_action Whether to + # @option options [required,:allow,:deny] :rule_action Whether to # allow or deny traffic that matches the rule. # - # @option options [required,Integer] :protocol IP protocol the rule - # applies to. You can use -1 to mean all protocols. You can see a - # list of # supported protocol numbers here: + # @option options [required,Integer] :protocol IP protocol the rule + # applies to. You can use -1 to mean all protocols. You can see a + # list of # supported protocol numbers here: # http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml # - # @option options [required,String] :cidr_block The CIDR range to + # @option options [required,String] :cidr_block The CIDR range to # allow or deny, in CIDR notation (e.g., 172.16.0.0/24). # - # @option options [Boolean] :egress (false) - # Whether this rule applies to egress traffic from the subnet (true) + # @option options [Boolean] :egress (false) + # Whether this rule applies to egress traffic from the subnet (true) # or ingress traffic to the subnet (false). # # @option options [Range] :port_range A numeric range - # of ports. Required if specifying TCP (6) or UDP (17) for the + # of ports. Required if specifying TCP (6) or UDP (17) for the # :protocol. # - # @option options [Integer] :icmp_code For the ICMP protocol, the - # ICMP code. You can use -1 to specify all ICMP codes for the given + # @option options [Integer] :icmp_code For the ICMP protocol, the + # ICMP code. You can use -1 to specify all ICMP codes for the given # ICMP type. # - # @option options [Integer] :icmp_type For the ICMP protocol, + # @option options [Integer] :icmp_type For the ICMP protocol, # the ICMP type. You can use -1 to specify all ICMP types. # # @return [nil] diff --git a/lib/aws/ec2/network_acl_collection.rb b/lib/aws/ec2/network_acl_collection.rb index 058d75dd9f9..c6144b10b31 100644 --- a/lib/aws/ec2/network_acl_collection.rb +++ b/lib/aws/ec2/network_acl_collection.rb @@ -32,7 +32,7 @@ def create options = {} client_opts = {} client_opts[:vpc_id] = vpc_id_option(options) - + resp = client.create_network_acl(client_opts) NetworkACL.new_from(:create_network_acl, resp.network_acl, @@ -51,7 +51,7 @@ def _each_item options = {}, &block response = filtered_request(:describe_network_acls, options, &block) response.network_acl_set.each do |a| - network_acl = NetworkACL.new_from(:describe_network_acls, a, + network_acl = NetworkACL.new_from(:describe_network_acls, a, a.network_acl_id, :config => config) yield(network_acl) diff --git a/lib/aws/ec2/network_interface.rb b/lib/aws/ec2/network_interface.rb index 05ab0e61b91..49596230d13 100644 --- a/lib/aws/ec2/network_interface.rb +++ b/lib/aws/ec2/network_interface.rb @@ -59,7 +59,7 @@ def initialize network_interface_id, options = {} alias_method :id, :network_interface_id attribute :vpc_id, :static => true - + attribute :subnet_id, :static => true mutable_attribute :description @@ -75,7 +75,7 @@ def initialize network_interface_id, options = {} attribute :mac_address, :static => true attribute :availability_zone_name, - :from => :availability_zone, + :from => :availability_zone, :static => true mutable_attribute :source_dest_check @@ -86,7 +86,7 @@ def initialize network_interface_id, options = {} alias_method :requester_managed?, :requester_managed - attribute :association do + attribute :association do translates_output {|assoc| assoc.to_hash } end @@ -172,7 +172,7 @@ def attachment # # @param [Hash] options # - # @option options [Integer] :device_index (1) The index of the device + # @option options [Integer] :device_index (1) The index of the device # for the network interface attachment on the instance. Defaults to 1. # # @return [nil] diff --git a/lib/aws/ec2/network_interface/attachment.rb b/lib/aws/ec2/network_interface/attachment.rb index dbeaff6b253..9d9665e9b8c 100644 --- a/lib/aws/ec2/network_interface/attachment.rb +++ b/lib/aws/ec2/network_interface/attachment.rb @@ -17,7 +17,7 @@ module AWS class EC2 class NetworkInterface < Resource class Attachment - + def initialize network_interface, details @network_interface = network_interface @attachment_id = details[:attachment_id] @@ -47,7 +47,7 @@ def initialize network_interface, details # @return [String] Returns the instance owner id. attr_reader :instance_owner_id - # @return [Integer] The index of the device for the network + # @return [Integer] The index of the device for the network # interface attachment on the instance. attr_reader :device_index diff --git a/lib/aws/ec2/network_interface_collection.rb b/lib/aws/ec2/network_interface_collection.rb index ce0d78c735d..83f2917301a 100644 --- a/lib/aws/ec2/network_interface_collection.rb +++ b/lib/aws/ec2/network_interface_collection.rb @@ -32,7 +32,7 @@ class NetworkInterfaceCollection < Collection # network interface. # # @option options [Array,Array] :security_groups - # A list of security groups (or security group id strings) that + # A list of security groups (or security group id strings) that # should be used by this network interface. # # @return [NetworkInterface] @@ -51,12 +51,12 @@ def create options = {} groups = groups_options(options) client_opts[:groups] = groups if groups - + resp = client.create_network_interface(client_opts) NetworkInterface.new_from(:create_network_interface, - resp.network_interface, - resp.network_interface.network_interface_id, + resp.network_interface, + resp.network_interface.network_interface_id, :config => config) end @@ -90,7 +90,7 @@ def _each_item options = {}, &block resp.network_interface_set.each do |n| network_interface = NetworkInterface.new_from( - :describe_network_interfaces, n, + :describe_network_interfaces, n, n.network_interface_id, :config => config) yield(network_interface) diff --git a/lib/aws/ec2/permission_collection.rb b/lib/aws/ec2/permission_collection.rb index ac30b3b7a14..a7db4869af6 100644 --- a/lib/aws/ec2/permission_collection.rb +++ b/lib/aws/ec2/permission_collection.rb @@ -75,7 +75,7 @@ def private? # otherwise the resource is made private. # @return [nil] def public= value - params = value ? + params = value ? { :add => [{ :group => "all" }] } : { :remove => [{ :group => "all" }] } client.send(modify_call, modify_params(params)) diff --git a/lib/aws/ec2/reserved_instances_collection.rb b/lib/aws/ec2/reserved_instances_collection.rb index 5f15e427b3a..6757ecdc845 100644 --- a/lib/aws/ec2/reserved_instances_collection.rb +++ b/lib/aws/ec2/reserved_instances_collection.rb @@ -14,7 +14,7 @@ module AWS class EC2 class ReservedInstancesCollection < Collection - + include TaggedCollection def member_class @@ -27,7 +27,7 @@ def each &block response.reserved_instances_set.each do |item| reserved_instance = ReservedInstances.new_from( - :describe_reserved_instances, item, + :describe_reserved_instances, item, item.reserved_instances_id, :config => config) yield(reserved_instance) diff --git a/lib/aws/ec2/reserved_instances_offering_collection.rb b/lib/aws/ec2/reserved_instances_offering_collection.rb index 3c1a603866c..f44edfbe9c6 100644 --- a/lib/aws/ec2/reserved_instances_offering_collection.rb +++ b/lib/aws/ec2/reserved_instances_offering_collection.rb @@ -14,7 +14,7 @@ module AWS class EC2 class ReservedInstancesOfferingCollection < Collection - + include TaggedCollection def member_class @@ -26,7 +26,7 @@ def each &block response.reserved_instances_offerings_set.each do |item| reserved_instance_offering = ReservedInstancesOffering.new_from( - :describe_reserved_instances_offerings, item, + :describe_reserved_instances_offerings, item, item.reserved_instances_offering_id, :config => config) yield(reserved_instance_offering) diff --git a/lib/aws/ec2/route_table.rb b/lib/aws/ec2/route_table.rb index 8c1a0386103..8193c1af8ef 100644 --- a/lib/aws/ec2/route_table.rb +++ b/lib/aws/ec2/route_table.rb @@ -49,7 +49,7 @@ def initialize route_table_id, options = {} resp.route_table_set.find{|t| t.route_table_id == route_table_id } end - # @return [Boolean] Returns true if this is the main (default) + # @return [Boolean] Returns true if this is the main (default) # route table. def main? @main = !!associations.find{|a| a.main? } if @main.nil? @@ -61,7 +61,7 @@ def vpc VPC.new(vpc_id, :config => config) end - # @return [Array] Returns an array of subnets ({Subnet}) + # @return [Array] Returns an array of subnets ({Subnet}) # that currently associated to this route table. def subnets @@ -69,7 +69,7 @@ def subnets # The default route table has a single association where #subnet # returns nil (the main association). If this is not the main - # route table we can safely return the subnets. + # route table we can safely return the subnets. return subnets unless subnets.include?(nil) subnets.compact! @@ -82,13 +82,13 @@ def subnets all_subnets = vpc.subnets.to_a # subnets assigned directly to a route table - associated_subnets = vpc.route_tables. + associated_subnets = vpc.route_tables. map(&:associations).flatten. map(&:subnet).flatten. compact # subnets NOT assigned to a route table, these default as - # belonging to the default route table through the "main" + # belonging to the default route table through the "main" # association unassociated_subnets = all_subnets.inject([]) do |list,subnet| unless associated_subnets.include?(subnet) @@ -103,17 +103,17 @@ def subnets end - # @return [Array] Returns an array of + # @return [Array] Returns an array of # {RouteTable::Association} objects (association to subnets). def associations - association_set.collect do |details| - Association.new(self, - details[:route_table_association_id], + association_set.collect do |details| + Association.new(self, + details[:route_table_association_id], details[:subnet_id]) end end - # @return [Array] Returns an array of routes ({Route} objects) + # @return [Array] Returns an array of routes ({Route} objects) # belonging to this route table. def routes route_set.map do |route_details| @@ -124,8 +124,8 @@ def routes # Creates a new route in this route route. The route must be attached # to a gateway, instance or network interface. # - # @param [String] destination_cidr_block The CIDR address block - # used for the destination match. For example: 0.0.0.0/0. + # @param [String] destination_cidr_block The CIDR address block + # used for the destination match. For example: 0.0.0.0/0. # Routing decisions are based on the most specific match. # # @param [Hash] options @@ -165,7 +165,7 @@ def delete_route destination_cidr_block nil end - # Deletes this route table. The route table must not be + # Deletes this route table. The route table must not be # associated with a subnet. You can't delete the main route table. # @return [nil] def delete @@ -174,7 +174,7 @@ def delete end protected - + def route_options destination_cidr_block, options = {} client_opts = {} diff --git a/lib/aws/ec2/route_table/association.rb b/lib/aws/ec2/route_table/association.rb index 2f7b5582880..18fa5191422 100644 --- a/lib/aws/ec2/route_table/association.rb +++ b/lib/aws/ec2/route_table/association.rb @@ -61,13 +61,13 @@ class RouteTable < Resource # delete an association, the subnet becomes associated with the # main route table. # - # # delete all explicit route table associations -- as a result + # # delete all explicit route table associations -- as a result # # all subnets will default to the main route table # vpc.subnets.each do |subnet| # assoc = subnet.route_table_association # assoc.delete unless assoc.main? # end - # + # class Association # @private @@ -83,7 +83,7 @@ def initialize route_table, association_id, subnet_id end end - # @return [String] An identifier representing the association + # @return [String] An identifier representing the association # between the network ACL and subnet. attr_reader :association_id @@ -93,7 +93,7 @@ def initialize route_table, association_id, subnet_id attr_reader :route_table # @return [Subnet,nil] Returns the subnet this association belongs. - # If this is the main (default) association, then this method + # If this is the main (default) association, then this method # returns nil. attr_reader :subnet diff --git a/lib/aws/ec2/route_table/route.rb b/lib/aws/ec2/route_table/route.rb index e3d2e71ec59..ee69640a9d9 100644 --- a/lib/aws/ec2/route_table/route.rb +++ b/lib/aws/ec2/route_table/route.rb @@ -20,9 +20,9 @@ class EC2 # ec2 = AWS::EC2.new # route_table = ec2.route_tables.first # route_table.routes.each do |route| - # # ... + # # ... # end - # + # class RouteTable < Resource class Route @@ -47,7 +47,7 @@ def initialize route_table, details if details[:network_interface_id] @network_interface = NetworkInterface.new( - details[:network_interface_id], + details[:network_interface_id], :vpc_id => route_table.vpc_id, :config => route_table.config) end diff --git a/lib/aws/ec2/route_table_collection.rb b/lib/aws/ec2/route_table_collection.rb index 6b13edd6989..c3a167f3ed9 100644 --- a/lib/aws/ec2/route_table_collection.rb +++ b/lib/aws/ec2/route_table_collection.rb @@ -24,7 +24,7 @@ class RouteTableCollection < Collection # @param [Hash] options # # @option options [VPC,String] :vpc The vpc or vpc id of where you want - # to create the route table. + # to create the route table. # # @return [RouteTable] # @@ -32,7 +32,7 @@ def create options = {} client_opts = {} client_opts[:vpc_id] = vpc_id_option(options) - + resp = client.create_route_table(client_opts) RouteTable.new_from(:create_route_table, resp.route_table, @@ -59,7 +59,7 @@ def _each_item options = {}, &block response = filtered_request(:describe_route_tables, options, &block) response.route_table_set.each do |t| - route_table = RouteTable.new_from(:describe_route_tables, t, + route_table = RouteTable.new_from(:describe_route_tables, t, t.route_table_id, :config => config) yield(route_table) diff --git a/lib/aws/ec2/security_group_collection.rb b/lib/aws/ec2/security_group_collection.rb index 2a9372a054c..2695d1454bc 100644 --- a/lib/aws/ec2/security_group_collection.rb +++ b/lib/aws/ec2/security_group_collection.rb @@ -19,15 +19,15 @@ class SecurityGroupCollection < Collection include TaggedCollection - # Creates a new + # Creates a new # @param [String] name The name of the security group to create. # @param [Hash] options - # @option options [String] :description An informal description + # @option options [String] :description An informal description # of this security group. Accepts alphanumeric characters, spaces, # dashes, and underscores. If left blank the description will be set # to the name. # - # @option options [VPC,String] :vpc (nil) A VPC or VPC id string to + # @option options [VPC,String] :vpc (nil) A VPC or VPC id string to # create the security group in. When specified a VPC security # group is created. # @@ -68,7 +68,7 @@ def [] group_id # A subsequent call to #each will limit the security groups returned # by the set of filters. # - # If you supply multiple values to #filter then these values are + # If you supply multiple values to #filter then these values are # treated as an OR condition. To return security groups named # 'test' or 'fake': # @@ -83,7 +83,7 @@ def [] group_id # filter('group-name', '*ruby*').each do |group| # #... # end - # + # # Note that * matches one or more characters and ? matches any one # character. # @@ -92,17 +92,17 @@ def [] group_id # * description - Description of the security group. # * group-id - ID of the security group. # * group-name - Name of the security group. - # * ip-permission.cidr - CIDR range that has been granted the + # * ip-permission.cidr - CIDR range that has been granted the # permission. - # * ip-permission.from-port - Start of port range for the TCP and UDP + # * ip-permission.from-port - Start of port range for the TCP and UDP # protocols, or an ICMP type number. - # * ip-permission.group-name - Name of security group that has been + # * ip-permission.group-name - Name of security group that has been # granted the permission. - # * ip-permission.protocol - IP protocol for the permission. Valid + # * ip-permission.protocol - IP protocol for the permission. Valid # values include 'tcp', 'udp', 'icmp' or a protocol number. - # * ip-permission.to-port - End of port range for the TCP and UDP + # * ip-permission.to-port - End of port range for the TCP and UDP # protocols, or an ICMP code. - # * ip-permission.user-id - ID of AWS account that has been granted + # * ip-permission.user-id - ID of AWS account that has been granted # the permission. # * owner-id - AWS account ID of the owner of the security group. # * tag-key - Key of a tag assigned to the security group. @@ -111,7 +111,7 @@ def [] group_id # @return [SecurityGroupCollection] A new collection that represents # a subset of the security groups associated with this account. - # Yields once for each security group in this account. + # Yields once for each security group in this account. # # @yield [group] # @yieldparam [SecurityGroup] group @@ -121,7 +121,7 @@ def each &block response = filtered_request(:describe_security_groups) response.security_group_info.each do |info| - group = SecurityGroup.new_from(:describe_security_groups, info, + group = SecurityGroup.new_from(:describe_security_groups, info, info.group_id, :config => config) yield(group) diff --git a/lib/aws/ec2/subnet.rb b/lib/aws/ec2/subnet.rb index c283d5dc1ec..af456673253 100644 --- a/lib/aws/ec2/subnet.rb +++ b/lib/aws/ec2/subnet.rb @@ -82,7 +82,7 @@ def network_acl_association associations.first{|a| a.subnet == self } end - # @return [RouteTable] Returns the route table currently associated + # @return [RouteTable] Returns the route table currently associated # with this subnet. def route_table route_table_association.route_table @@ -111,7 +111,7 @@ def set_route_table route_table if assoc.main? client_opts[:subnet_id] = subnet_id response = client.associate_route_table(client_opts) - association_id = response.association_id + association_id = response.association_id else client_opts[:association_id] = assoc.association_id resp = client.replace_route_table_association(client_opts) diff --git a/lib/aws/ec2/subnet_collection.rb b/lib/aws/ec2/subnet_collection.rb index 12452caadf2..e129574d04f 100644 --- a/lib/aws/ec2/subnet_collection.rb +++ b/lib/aws/ec2/subnet_collection.rb @@ -41,7 +41,7 @@ class EC2 # # subnet = subnets['subnet-id-here'] # - # You can filter subnets as well. See the EC2 API documentation + # You can filter subnets as well. See the EC2 API documentation # (http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeSubnets.html) for a complete list of accepted filters. # # subnet = subnets.filter('state', 'available').first @@ -53,7 +53,7 @@ class SubnetCollection < Collection # Creates a Subnet. Subnets require a valid CIDR block and # are created inside a VPC. If this collection does not - # have a + # have a # # @param [String] cidr_block The CIDR block you want the subnet to # cover (e.g., 10.0.0.0/24). @@ -63,8 +63,8 @@ class SubnetCollection < Collection # @option options [VPC,String] :vpc The VPC (or VPC id string) to # create the subnet in. # - # @option options [String,AvailabilityZone] :availability_zone - # The Availability Zone you want the subnet in. + # @option options [String,AvailabilityZone] :availability_zone + # The Availability Zone you want the subnet in. # AWS selects a default zone for you (recommended). # # @return [Subnet] @@ -74,12 +74,12 @@ def create cidr_block, options = {} client_opts = {} client_opts[:vpc_id] = vpc_id_option(options) client_opts[:cidr_block] = cidr_block - client_opts[:availability_zone] = az_option(options) if + client_opts[:availability_zone] = az_option(options) if options[:availability_zone] resp = client.create_subnet(client_opts) - Subnet.new_from(:create_subnet, resp.subnet, + Subnet.new_from(:create_subnet, resp.subnet, resp.subnet.subnet_id, :config => config) end @@ -93,7 +93,7 @@ def [] subnet_id protected def az_option options options[:availability_zone].is_a?(AvailabilityZone) ? - options[:availability_zone].name : + options[:availability_zone].name : options[:availability_zone] end @@ -102,7 +102,7 @@ def _each_item options = {}, &block response = filtered_request(:describe_subnets, options, &block) response.subnet_set.each do |s| - subnet = Subnet.new_from(:describe_subnets, + subnet = Subnet.new_from(:describe_subnets, s, s.subnet_id, :config => config) yield(subnet) diff --git a/lib/aws/ec2/tag_collection.rb b/lib/aws/ec2/tag_collection.rb index 8149cdf259c..1ec0ccc8233 100644 --- a/lib/aws/ec2/tag_collection.rb +++ b/lib/aws/ec2/tag_collection.rb @@ -55,7 +55,7 @@ class TagCollection < Collection # @param [Object] resource The item to tag. This should be a taggable # EC2 resource, like an instance, security group, etc. # @param [String] key The tag key (or name). - # @param [Hash] options + # @param [Hash] options # @option optins [String] :value ('') The optional tag value. When # left blank its assigned the empty string. # @return [Tag] diff --git a/lib/aws/ec2/tagged_collection.rb b/lib/aws/ec2/tagged_collection.rb index b848a76cd7a..605a91e4637 100644 --- a/lib/aws/ec2/tagged_collection.rb +++ b/lib/aws/ec2/tagged_collection.rb @@ -14,7 +14,7 @@ module AWS class EC2 - # Most of the AWS::EC2 collections include TaggedCollection. This + # Most of the AWS::EC2 collections include TaggedCollection. This # module provides methods for filtering the collection with # tags. # @@ -23,7 +23,7 @@ class EC2 module TaggedCollection # Filter the collection by one or more tag keys. If you pass multiple - # tag keys they will be be treated as OR conditions. If you want to + # tag keys they will be be treated as OR conditions. If you want to # AND them together call tagged multiple times (chained). # # Filter the collection to items items tagged 'live' OR 'test' @@ -39,7 +39,7 @@ def tagged *keys end # Filter the collection by one or more tag values. If you pass multiple - # tag values they will be be treated as OR conditions. If you want to + # tag values they will be be treated as OR conditions. If you want to # AND them together call tagged multiple times (chained). # # collection.tagged('stage').tagged_values('production') diff --git a/lib/aws/ec2/tagged_item.rb b/lib/aws/ec2/tagged_item.rb index 1ce0bfc648d..ed9893d72b6 100644 --- a/lib/aws/ec2/tagged_item.rb +++ b/lib/aws/ec2/tagged_item.rb @@ -29,7 +29,7 @@ module TaggedItem # @return [Tag] The tag that was created. def add_tag key, options = {} client.create_tags({ - :resources => [id], + :resources => [id], :tags => [{ :key => key, :value => options[:value].to_s }], }) Tag.new(self, key, options.merge(:config => config)) diff --git a/lib/aws/ec2/volume.rb b/lib/aws/ec2/volume.rb index db95320e900..f24e5236f20 100644 --- a/lib/aws/ec2/volume.rb +++ b/lib/aws/ec2/volume.rb @@ -144,7 +144,7 @@ def detach_from(instance, device, options = {}) def exists? resp = client.describe_volumes(:filters => [ { :name => 'volume-id', :values => [id] } - ]) + ]) resp.volume_index.key?(id) end diff --git a/lib/aws/ec2/volume_collection.rb b/lib/aws/ec2/volume_collection.rb index 9904ff8ef53..76cf80cb344 100644 --- a/lib/aws/ec2/volume_collection.rb +++ b/lib/aws/ec2/volume_collection.rb @@ -38,7 +38,7 @@ def each(&block) resp = filtered_request(:describe_volumes) resp.volume_set.each do |v| - volume = Volume.new_from(:describe_volumes, v, + volume = Volume.new_from(:describe_volumes, v, v.volume_id, :config => config) yield(volume) diff --git a/lib/aws/ec2/vpc.rb b/lib/aws/ec2/vpc.rb index 3b6add6b826..31f07e8c634 100644 --- a/lib/aws/ec2/vpc.rb +++ b/lib/aws/ec2/vpc.rb @@ -68,7 +68,7 @@ def security_groups SecurityGroupCollection.new(:config => config).filter('vpc-id', vpc_id) end - # @return [SubnetCollection] Returns a filtered collection of + # @return [SubnetCollection] Returns a filtered collection of # subnets that are in this VPC. def subnets SubnetCollection.new(:config => config).filter('vpc-id', vpc_id) @@ -120,12 +120,12 @@ def internet_gateway= internet_gateway if internet_gateway unless internet_gateway.is_a?(InternetGateway) - internet_gateway = InternetGateway.new(internet_gateway, + internet_gateway = InternetGateway.new(internet_gateway, :config => config) end internet_gateway.attach(self) end - + end # @return [VPNGateway,nil] Returns the vpn gateway attached to @@ -136,7 +136,7 @@ def vpn_gateway gateways.filter('attachment.vpc-id', vpc_id).first end - # @return [DHCPOptions] Returns the dhcp options associated with + # @return [DHCPOptions] Returns the dhcp options associated with # this VPC. def dhcp_options DHCPOptions.new(dhcp_options_id, :config => config) @@ -153,9 +153,9 @@ def dhcp_options # # @param [DHCPOptions,String] dhcp_options A {DHCPOptions} object # or a dhcp options id string. - # + # def dhcp_options= dhcp_options - unless dhcp_options.is_a?(DHCPOptions) + unless dhcp_options.is_a?(DHCPOptions) dhcp_options = DHCPOptions.new(dhcp_options, :config => config) end dhcp_options.associate(self) diff --git a/lib/aws/ec2/vpc_collection.rb b/lib/aws/ec2/vpc_collection.rb index 7d558b9274b..7aab0e588e3 100644 --- a/lib/aws/ec2/vpc_collection.rb +++ b/lib/aws/ec2/vpc_collection.rb @@ -19,27 +19,27 @@ class VPCCollection < Collection include TaggedCollection include Core::Collection::Simple - # Creates a VPC with the CIDR block you specify. The smallest VPC you - # can create uses a /28 netmask (16 IP addresses), and the largest - # uses a /16 netmask (65,536 IP addresses). + # Creates a VPC with the CIDR block you specify. The smallest VPC you + # can create uses a /28 netmask (16 IP addresses), and the largest + # uses a /16 netmask (65,536 IP addresses). # # vpc = ec2.vpcs.create('10.0.0.0/16') # - # @param [String] cidr_block The CIDR block you want the VPC to + # @param [String] cidr_block The CIDR block you want the VPC to # cover (e.g., 10.0.0.0/16). # # @param [Hash] options # - # @option options [Boolean] :instance_tenancy (:default) - # The allowed tenancy of instances launched into the VPC. A value of - # +:default+ means instances can be launched with any tenancy; a value - # of +:dedicated+ means all instances launched into the VPC will be launched with + # @option options [Boolean] :instance_tenancy (:default) + # The allowed tenancy of instances launched into the VPC. A value of + # +:default+ means instances can be launched with any tenancy; a value + # of +:dedicated+ means all instances launched into the VPC will be launched with # dedicated tenancy regardless of the tenancy assigned to the instance at launch. # # @return [VPC] # def create cidr_block, options = {} - + tenancy = options.key?(:instance_tenancy) ? options[:instance_tenancy].to_s : 'default' diff --git a/lib/aws/ec2/vpn_connection.rb b/lib/aws/ec2/vpn_connection.rb index 67d2fbfd0eb..189ccedcd0a 100644 --- a/lib/aws/ec2/vpn_connection.rb +++ b/lib/aws/ec2/vpn_connection.rb @@ -25,8 +25,8 @@ class EC2 # @attr_reader [String] customer_gateway_id # # @attr_reader [String] customer_gateway_configuration - # Configuration XML for the VPN connection's customer gateway This - # attribute is always present after creating a vpn connection while + # Configuration XML for the VPN connection's customer gateway This + # attribute is always present after creating a vpn connection while # the connection state is :pending or :available. # class VPNConnection < Resource @@ -57,13 +57,13 @@ def initialize vpn_connection_id, options = {} attribute :vgw_telemetry_details, :from => :vgw_telemetry protected :vgw_telemetry_details - + populates_from(:create_vpn_connection) do |resp| resp.vpn_connection if resp.vpn_connection.vpn_connection_id == id end populates_from(:describe_vpn_connections) do |resp| - resp.vpn_connection_set.find do |vpn_connection| + resp.vpn_connection_set.find do |vpn_connection| vpn_connection.vpn_connection_id == vpn_connection_id end end diff --git a/lib/aws/ec2/vpn_connection/telemetry.rb b/lib/aws/ec2/vpn_connection/telemetry.rb index 26b7d0e8cbc..aef01eb1730 100644 --- a/lib/aws/ec2/vpn_connection/telemetry.rb +++ b/lib/aws/ec2/vpn_connection/telemetry.rb @@ -15,7 +15,7 @@ module AWS class EC2 class VPNConnection < Resource class Telemetry - + def initialize vpn_connection, details @vpn_connection = vpn_connection @outside_ip_address = details.outside_ip_address diff --git a/lib/aws/ec2/vpn_connection_collection.rb b/lib/aws/ec2/vpn_connection_collection.rb index 90ad2175415..5884a5306ab 100644 --- a/lib/aws/ec2/vpn_connection_collection.rb +++ b/lib/aws/ec2/vpn_connection_collection.rb @@ -19,7 +19,7 @@ class VPNConnectionCollection < Collection include TaggedCollection include Core::Collection::Simple - # Creates a new VPN connection between an existing virtual private + # Creates a new VPN connection between an existing virtual private # gateway and a VPN customer gateway. # # @param [Hash] options @@ -30,13 +30,13 @@ class VPNConnectionCollection < Collection # @option options [VPNGateway,String] :vpn_gateway # The {VPNGateway} object or vpn gateway id string. # - # @option options [String] :vpn_type ('ipsec.1') + # @option options [String] :vpn_type ('ipsec.1') # The type of VPN connection. # # @return [VPNConnection] # def create options = {} - + client_opts = {} client_opts[:customer_gateway_id] = customer_gateway_id(options) client_opts[:vpn_gateway_id] = vpn_gateway_id(options) @@ -52,7 +52,7 @@ def create options = {} # Returns a reference to the VPN connection with the given id. # # vpn_connection = ec2.vpn_connections['vpn-connection-id'] - # + # # @param [String] vpn_connection_id # # @return [VPNConnection] @@ -67,7 +67,7 @@ def _each_item options = {}, &block response = filtered_request(:describe_vpn_connections, options, &block) response.vpn_connection_set.each do |c| - vpn_connection = VPNConnection.new_from(:describe_vpn_connections, + vpn_connection = VPNConnection.new_from(:describe_vpn_connections, c, c.vpn_connection_id, :config => config) yield(vpn_connection) diff --git a/lib/aws/ec2/vpn_gateway.rb b/lib/aws/ec2/vpn_gateway.rb index 9563640c29a..83d5327df58 100644 --- a/lib/aws/ec2/vpn_gateway.rb +++ b/lib/aws/ec2/vpn_gateway.rb @@ -43,7 +43,7 @@ def initialize vpn_gateway_id, options = {} end populates_from(:describe_vpn_gateways) do |resp| - resp.vpn_gateway_set.find do |gateway| + resp.vpn_gateway_set.find do |gateway| gateway.vpn_gateway_id == vpn_gateway_id end end @@ -56,7 +56,7 @@ def attachments # @return [VPC,nil] Returns the currently attached VPC, or nil # if this gateway has not been attached. def vpc - if attachment = attachments.first + if attachment = attachments.first attachment.vpc end end diff --git a/lib/aws/ec2/vpn_gateway_collection.rb b/lib/aws/ec2/vpn_gateway_collection.rb index 34ee1c1fbfe..401f6542238 100644 --- a/lib/aws/ec2/vpn_gateway_collection.rb +++ b/lib/aws/ec2/vpn_gateway_collection.rb @@ -19,20 +19,20 @@ class VPNGatewayCollection < Collection include TaggedCollection include Core::Collection::Simple - # Creates a new virtual private gateway. A virtual private gateway is - # the VPC-side endpoint for your VPN connection. You can create a + # Creates a new virtual private gateway. A virtual private gateway is + # the VPC-side endpoint for your VPN connection. You can create a # virtual private gateway before creating the VPC itself. # # @param [Hash] options # - # @option options [String] :vpn_type ('ipsec.1') The type of VPN + # @option options [String] :vpn_type ('ipsec.1') The type of VPN # connection this virtual private gateway supports. # # @option options [AvailabilityZone,String] :availability_zone # The Availability Zone where you want the virtual private gateway. # AWS can select a default zone for you. This can be an # {AvailabilityZone} object or availability zone name string. - # + # # @return [VPNGateway] # def create options = {} @@ -64,7 +64,7 @@ def _each_item options = {}, &block response = filtered_request(:describe_vpn_gateways, options, &block) response.vpn_gateway_set.each do |g| - gateway = VPNGateway.new_from(:describe_vpn_gateways, g, + gateway = VPNGateway.new_from(:describe_vpn_gateways, g, g.vpn_gateway_id, :config => config) yield(gateway) diff --git a/lib/aws/elb.rb b/lib/aws/elb.rb index 5e4610112a1..58894df0392 100644 --- a/lib/aws/elb.rb +++ b/lib/aws/elb.rb @@ -16,7 +16,7 @@ module AWS - # Provides an expressive, object-oriented interface to Elastic Load + # Provides an expressive, object-oriented interface to Elastic Load # Balancing (ELB). # # == Credentials diff --git a/lib/aws/elb/availability_zone_collection.rb b/lib/aws/elb/availability_zone_collection.rb index 2f1710f97b9..d830f8f8678 100644 --- a/lib/aws/elb/availability_zone_collection.rb +++ b/lib/aws/elb/availability_zone_collection.rb @@ -14,7 +14,7 @@ module AWS class ELB - # A collection that help maanage the availability zones for + # A collection that help maanage the availability zones for # a load balancer. # # load_balancer = AWS::ELB.new.load_balancers['my-load-balancer'] @@ -36,7 +36,7 @@ class AvailabilityZoneCollection include Core::Collection::Simple - # @param [LoadBalancer] load_balancer The load balancer this list of + # @param [LoadBalancer] load_balancer The load balancer this list of # availability zones belongs to. def initialize load_balancer, options = {} @load_balancer = load_balancer @@ -56,16 +56,16 @@ def initialize load_balancer, options = {} # zones = AWS::EC2.new.availability_zones.to_a # load_balancer.availability_zones.enable(zones) # - # The load balancer evenly distributes requests across all its - # registered availability zones that contain instances. As a result, - # the client must ensure that its load balancer is appropriately + # The load balancer evenly distributes requests across all its + # registered availability zones that contain instances. As a result, + # the client must ensure that its load balancer is appropriately # scaled for each registered Availability Zone. # # @param [String,EC2::AvailabilityZone] availability_zones One or more # availability zone names (strings) or objects {EC2::AvailabilityZone}. # # @return [nil] - # + # def enable *availability_zones names = availability_zones.flatten.collect do |av| @@ -80,7 +80,7 @@ def enable *availability_zones end - # Removes the specified EC2 availability zones from the set of + # Removes the specified EC2 availability zones from the set of # configured availability zones for the load balancer. # # load_balancer.availability_zones.disable("us-west-2a", "us-west-2b") @@ -91,22 +91,22 @@ def enable *availability_zones # zones = AWS::EC2.new.availability_zones.to_a # load_balancer.availability_zones.disable(zones) # - # There must be at least one availability zone registered with a - # load balancer at all times. A client cannot remove all the availability - # zones from a load balancer. Once an availability zone is removed, - # all the instances registered with the load balancer that are in the - # removed availability zone go into the out of service state. + # There must be at least one availability zone registered with a + # load balancer at all times. A client cannot remove all the availability + # zones from a load balancer. Once an availability zone is removed, + # all the instances registered with the load balancer that are in the + # removed availability zone go into the out of service state. # - # Upon availability zone removal, the load balancer attempts to - # equally balance the traffic among its remaining usable availability - # zones. Trying to remove an availability zone that was not + # Upon availability zone removal, the load balancer attempts to + # equally balance the traffic among its remaining usable availability + # zones. Trying to remove an availability zone that was not # associated with the load balancer does nothing. # # @param [String,EC2::AvailabilityZone] availability_zones One or more # availability zone names (strings) or objects {EC2::AvailabilityZone}. # # @return [nil] - # + # def disable *availability_zones names = availability_zones.flatten.collect do |av| diff --git a/lib/aws/elb/errors.rb b/lib/aws/elb/errors.rb index 4738055c453..062bb37f6ba 100644 --- a/lib/aws/elb/errors.rb +++ b/lib/aws/elb/errors.rb @@ -17,7 +17,7 @@ module Errors extend Core::LazyErrorClasses - def self.error_class code + def self.error_class code super(code.sub(/^ElasticLoadBalancing\./, '')) end diff --git a/lib/aws/elb/load_balancer.rb b/lib/aws/elb/load_balancer.rb index f28fe1bdab8..902081665aa 100644 --- a/lib/aws/elb/load_balancer.rb +++ b/lib/aws/elb/load_balancer.rb @@ -38,7 +38,7 @@ class ELB # This attribute it set only for LoadBalancers attached to an Amazon VPC. # If the Scheme is 'internet-facing', the LoadBalancer has a publicly # resolvable DNS name that resolves to public IP addresses. - # If the Scheme is 'internal', the LoadBalancer has a publicly + # If the Scheme is 'internal', the LoadBalancer has a publicly # resolvable DNS name that resolves to private IP addresses. # # @attr_reader [Array] subnet_ids Provides a list of VPC subnet IDs diff --git a/lib/aws/elb/load_balancer_collection.rb b/lib/aws/elb/load_balancer_collection.rb index 59d99db1e49..17d53a973b2 100644 --- a/lib/aws/elb/load_balancer_collection.rb +++ b/lib/aws/elb/load_balancer_collection.rb @@ -46,7 +46,7 @@ class LoadBalancerCollection # one or more availability zones. Values may be availability zone # name strings, or {AWS::EC2::AvailabilityZone} objects. # - # @option options [required,Array] :listeners An array of load + # @option options [required,Array] :listeners An array of load # balancer listener options. Each value must be an array with the # following keys: # * +:port+ @@ -70,7 +70,7 @@ class LoadBalancerCollection # your load balancer within your VPC. This can be an array of # security group ids or {EC2::SecurityGroup} objects. VPC only. # - # @option options [String] :scheme ('internal' The type of a load + # @option options [String] :scheme ('internal' The type of a load # balancer. Accepts 'internet-facing' or 'internal'. VPC only. # def create name, options = {} diff --git a/lib/aws/emr/instance_group_collection.rb b/lib/aws/emr/instance_group_collection.rb index 0ad7976f293..708ad0a3fc7 100644 --- a/lib/aws/emr/instance_group_collection.rb +++ b/lib/aws/emr/instance_group_collection.rb @@ -42,7 +42,7 @@ def [] instance_group_id # @option options [String] :market Market type of the Amazon EC2 # instances used to create a cluster node. # @option opitons [Float,String] :bid_price Bid price for each Amazon - # EC2 instance in the instance group when launching nodes as + # EC2 instance in the instance group when launching nodes as # spot instances, expressed in USD. # @return [InstanceGroup] def create role, instance_type, instance_count, options = {} diff --git a/lib/aws/emr/job_flow.rb b/lib/aws/emr/job_flow.rb index 61cab2d161d..8282e7cb46a 100644 --- a/lib/aws/emr/job_flow.rb +++ b/lib/aws/emr/job_flow.rb @@ -251,7 +251,7 @@ def enable_termination_protection set_termination_protection(true) end - # Removes a lock on this job flow so the Amazon EC2 instances in the + # Removes a lock on this job flow so the Amazon EC2 instances in the # cluster may be terminated. # @return [nil] def disable_termination_protection diff --git a/lib/aws/errors.rb b/lib/aws/errors.rb index f0cdff2fba4..2703424c261 100644 --- a/lib/aws/errors.rb +++ b/lib/aws/errors.rb @@ -32,12 +32,12 @@ module AWS # A 500 level error typically indicates the service is having an issue. # # Requests that generate service errors are automatically retried with - # an exponential backoff. If the service still fails to respond with + # an exponential backoff. If the service still fails to respond with # a 200 after 3 retries the error is raised. # module Errors - # Base class for all errors returned by the service. + # Base class for all errors returned by the service. class Base < StandardError # @overload new(error_message) @@ -133,7 +133,7 @@ def initialize msg = nil * Export AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to ENV -* On EC2 you can run instances with an IAM instance profile and credentials +* On EC2 you can run instances with an IAM instance profile and credentials will be auto loaded from the instance metadata service on those instances. @@ -143,7 +143,7 @@ def initialize msg = nil = Ruby on Rails -In a Ruby on Rails application you may also specify your credentials in +In a Ruby on Rails application you may also specify your credentials in the following ways: * Via a config initializer script using any of the methods mentioned above diff --git a/lib/aws/glacier/vault_collection.rb b/lib/aws/glacier/vault_collection.rb index 0d046198359..34f6962cf4b 100644 --- a/lib/aws/glacier/vault_collection.rb +++ b/lib/aws/glacier/vault_collection.rb @@ -59,7 +59,7 @@ def _each_item next_token, limit, options, &block vault = Vault.new_from(:list_vaults, v, v[:vault_name], - :config => config, + :config => config, :account_id => account_id) yield(vault) diff --git a/lib/aws/iam.rb b/lib/aws/iam.rb index a839550a378..a21cce6e541 100644 --- a/lib/aws/iam.rb +++ b/lib/aws/iam.rb @@ -16,7 +16,7 @@ module AWS - # This class is the starting point for working with + # This class is the starting point for working with # AWS Identity and Access Management (IAM). # # For more information about IAM: @@ -26,13 +26,13 @@ module AWS # # = Credentials # - # You can setup default credentials for all AWS services via + # You can setup default credentials for all AWS services via # AWS.config: # # AWS.config( # :access_key_id => 'YOUR_ACCESS_KEY_ID', # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') - # + # # Or you can set them directly on the IAM interface: # # iam = AWS::IAM.new( @@ -103,7 +103,7 @@ module AWS # = Users & Groups # # Each AWS account can have multiple users. Users can be used to easily - # manage permissions. Users can also be organized into groups. + # manage permissions. Users can also be organized into groups. # # user = iam.users.create('JohnDoe') # group = iam.groups.create('Developers') @@ -178,7 +178,7 @@ class IAM # iam.users.each do |user| # puts user.name # end - # + # # @return [UserCollection] Returns a collection that represents all of # the IAM users for this AWS account. def users @@ -203,7 +203,7 @@ def groups GroupCollection.new(:config => config) end - # Returns a collection that represents the access keys for this + # Returns a collection that represents the access keys for this # AWS account. # # iam = AWS::IAM.new @@ -218,7 +218,7 @@ def access_keys end # Returns a collection that represents the signing certificates - # for this AWS account. + # for this AWS account. # # iam = AWS::IAM.new # iam.signing_certificates.each do |cert| @@ -227,7 +227,7 @@ def access_keys # # If you need to access the signing certificates of a specific user, # see {User#signing_certificates}. - # + # # @return [SigningCertificateCollection] Returns a collection that # represents signing certificates for this AWS account. def signing_certificates @@ -286,7 +286,7 @@ def account_alias end # Deletes the account alias (if one exists). - # @return [nil] + # @return [nil] def remove_account_alias account_aliases.each do |account_alias| account_aliases.delete(account_alias) @@ -347,7 +347,7 @@ def account_summary # To change a user password, you must be using credentials from the # user you want to change: # - # # pass in a key pair generated for the user you want to change + # # pass in a key pair generated for the user you want to change # # the password for # iam = AWS::IAM.new(:access_key_id => '...', :secret_access_key => '...) # iam.change_password('old-password', 'new-password') @@ -368,7 +368,7 @@ def change_password old_password, new_password # Updates the account password policy for all IAM accounts. # @param [Hash] options - # @option options [Integer] :minimum_password_length + # @option options [Integer] :minimum_password_length # @option options [Boolean] :require_symbols # @option options [Boolean] :require_numbers # @option options [Boolean] :require_uppercase_characters diff --git a/lib/aws/iam/access_key.rb b/lib/aws/iam/access_key.rb index d699787cb10..2b558683ed0 100644 --- a/lib/aws/iam/access_key.rb +++ b/lib/aws/iam/access_key.rb @@ -18,7 +18,7 @@ class IAM # Status may be +:active+ or +:inactive+. # class AccessKey < Resource - + # @param [String] access_key_id The id of this access key. # @param [Hash] options # @option [String] :user The IAM user this access key belongs to. @@ -59,7 +59,7 @@ def initialize access_key_id, options = {} # Returns the secret access key. # - # You can only access the secret for newly created access + # You can only access the secret for newly created access # keys. Calling +secret+ on existing access keys raises an error. # # @example Getting the secret from a newly created access key @@ -74,7 +74,7 @@ def initialize access_key_id, options = {} # access_key.secret # #=> raises a runtime error # - # @return [String] Returns the secret access key. + # @return [String] Returns the secret access key. def secret secret_value or raise 'secret is only available for new access keys' end @@ -131,7 +131,7 @@ def delete nil end - # Returns a hash that should be saved somewhere safe. + # Returns a hash that should be saved somewhere safe. # # access_keys = iam.access_keys.create # access_keys.credentials @@ -143,7 +143,7 @@ def delete # s3.buckets.create('newbucket') # # @return [Hash] Returns a hash with the access key id and - # secret access key. + # secret access key. def credentials { :access_key_id => id, :secret_access_key => secret } end @@ -158,8 +158,8 @@ def resource_identifiers end # IAM does not provide a request for "get access keys". - # Also note, we do not page the response. This is because - # restrictions on how many access keys an account / user may + # Also note, we do not page the response. This is because + # restrictions on how many access keys an account / user may # have is fewer than one page of results. # @private protected diff --git a/lib/aws/iam/access_key_collection.rb b/lib/aws/iam/access_key_collection.rb index faec079ac40..d1c27b21101 100644 --- a/lib/aws/iam/access_key_collection.rb +++ b/lib/aws/iam/access_key_collection.rb @@ -25,13 +25,13 @@ class IAM # # # for an iam user # user_access_keys = iam.users['johndoe'].access_keys.create - # + # # == Secret # # Make sure after creating an access to retrieve the secret access key # and save it somewhere safe. # - # access_keys = iam.access_keys.create + # access_keys = iam.access_keys.create # secret = access_keys.secret # # If you try to access the secret on an access key that was not newly @@ -54,8 +54,8 @@ def initialize options = {} @user ? super(@user, options) : super(options) end - # @return [User,nil] Returns the user these accesss keys belong to. - # If this returns +nil+ then these access keys belong to the + # @return [User,nil] Returns the user these accesss keys belong to. + # If this returns +nil+ then these access keys belong to the # AWS account. attr_reader :user @@ -68,7 +68,7 @@ def create AccessKey.new_from(:create_access_key, resp.access_key, resp.access_key.access_key_id, new_options) - + end # @param [String] access_key_id The ID of the access key. @@ -79,7 +79,7 @@ def [] access_key_id end # Deletes all of the access keys from this collection. - # + # # iam.users['someuser'].access_keys.clear # # @return [nil] @@ -88,15 +88,15 @@ def clear nil end - # Yields once for each access key. You can limit the number of + # Yields once for each access key. You can limit the number of # access keys yielded using +:limit+. # # @param [Hash] options # @option options [Integer] :limit The maximum number of access keys # to yield. - # @option options [Integer] :batch_size The maximum number of + # @option options [Integer] :batch_size The maximum number of # access keys received each service reqeust. - # @yieldparam [AccessKey] access_key + # @yieldparam [AccessKey] access_key # @return [nil] def each options = {}, &block each_options = options.dup diff --git a/lib/aws/iam/config.rb b/lib/aws/iam/config.rb index eabb7315c1b..10d84a82c15 100644 --- a/lib/aws/iam/config.rb +++ b/lib/aws/iam/config.rb @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. AWS::Core::Configuration.module_eval do - + add_service 'IAM', 'iam', 'iam.amazonaws.com' - + end diff --git a/lib/aws/iam/mfa_device.rb b/lib/aws/iam/mfa_device.rb index f44e29eeda4..e5c7b1e7c0d 100644 --- a/lib/aws/iam/mfa_device.rb +++ b/lib/aws/iam/mfa_device.rb @@ -38,7 +38,7 @@ def initialize user, serial_number, options = {} # @return [nil] def deactivate client.deactivate_mfa_device({ - :user_name => user.name, + :user_name => user.name, :serial_number => serial_number, }) nil diff --git a/lib/aws/iam/mfa_device_collection.rb b/lib/aws/iam/mfa_device_collection.rb index 8520349af70..b412147bde2 100644 --- a/lib/aws/iam/mfa_device_collection.rb +++ b/lib/aws/iam/mfa_device_collection.rb @@ -29,11 +29,11 @@ def initialize user, options = {} attr_reader :user # Enables an MFA device for this user. - # @param [String] serial_number The serial number that uniquely + # @param [String] serial_number The serial number that uniquely # identifies the MFA device - # @param [String] authentication_code_1 An authentication code emitted + # @param [String] authentication_code_1 An authentication code emitted # by the device. - # @param [String] authentication_code_2 A subsequent authentication + # @param [String] authentication_code_2 A subsequent authentication # code emitted by the device. # @return [MFADevice] Returns the newly enabled MFA device. def enable serial_number, authentication_code_1, authentication_code_2 @@ -71,21 +71,21 @@ def [] serial_number # # @return [nil] def clear - each do |device| + each do |device| device.deactivate end nil end - # Yields once for each MFA device. + # Yields once for each MFA device. # # You can limit the number of devices yielded using +:limit+. # # @param [Hash] options # @option options [Integer] :limit The maximum number of devices to yield. - # @option options [Integer] :batch_size The maximum number of devices + # @option options [Integer] :batch_size The maximum number of devices # receive each service reqeust. - # @yieldparam [User] user + # @yieldparam [User] user # @return [nil] def each options = {}, &block super(options.merge(:user_name => user.name), &block) diff --git a/lib/aws/iam/resource.rb b/lib/aws/iam/resource.rb index 43b4ee33098..53e203f0095 100644 --- a/lib/aws/iam/resource.rb +++ b/lib/aws/iam/resource.rb @@ -47,7 +47,7 @@ class << self # @private def prefix_update_attributes prefix = 'new_' - @update_prefix = prefix + @update_prefix = prefix end # @private diff --git a/lib/aws/iam/server_certificate_collection.rb b/lib/aws/iam/server_certificate_collection.rb index d92540f5c22..028a0009322 100644 --- a/lib/aws/iam/server_certificate_collection.rb +++ b/lib/aws/iam/server_certificate_collection.rb @@ -101,7 +101,7 @@ def create options = {} resp = client.upload_server_certificate(client_opts) ServerCertificate.new( - resp[:server_certificate_metadata][:server_certificate_name], + resp[:server_certificate_metadata][:server_certificate_name], :config => config) end diff --git a/lib/aws/iam/signing_certificate.rb b/lib/aws/iam/signing_certificate.rb index 26f62faabae..d19607833bc 100644 --- a/lib/aws/iam/signing_certificate.rb +++ b/lib/aws/iam/signing_certificate.rb @@ -45,7 +45,7 @@ class IAM # user.name # #=> 'someuser' # - # @attr_reader [String] contents Returns the contents of this + # @attr_reader [String] contents Returns the contents of this # signing certificate. # # @attr_reader [Symbol] status The status of this signing @@ -65,9 +65,9 @@ def initialize certificate_id, options = {} # @return [String] Returns the signing certificate's ID. attr_reader :id - # @return [User,nil] Returns the user this cerficiate belongs to. - # Returns +nil+ if the cerficiate is a root credential for the - # account. If the configured credentials belong to an IAM user, + # @return [User,nil] Returns the user this cerficiate belongs to. + # Returns +nil+ if the cerficiate is a root credential for the + # account. If the configured credentials belong to an IAM user, # then that user is the implied owner. attr_reader :user @@ -78,7 +78,7 @@ def initialize certificate_id, options = {} protected :status= populates_from( - :upload_signing_certificate, + :upload_signing_certificate, :update_signing_certificate ) do |resp| resp.certificate if matches_response_object?(resp.certificate) @@ -146,9 +146,9 @@ def resource_identifiers identifiers end - # IAM does not provide a request for "get signing certificate". - # Also note, we do not page the response. This is because - # restrictions on how many certificates an account / user may + # IAM does not provide a request for "get signing certificate". + # Also note, we do not page the response. This is because + # restrictions on how many certificates an account / user may # have is fewer than one page of results. # @private protected diff --git a/lib/aws/iam/signing_certificate_collection.rb b/lib/aws/iam/signing_certificate_collection.rb index 8f75d010735..ca15a876838 100644 --- a/lib/aws/iam/signing_certificate_collection.rb +++ b/lib/aws/iam/signing_certificate_collection.rb @@ -49,13 +49,13 @@ def initialize options = {} @user ? super(@user, options) : super(options) end - # @return [User,nil] Returns the user this collection belongs to. + # @return [User,nil] Returns the user this collection belongs to. # Returns +nil+ if the collection represents the root credentials - # for the account. If the configured credentials belong to an + # for the account. If the configured credentials belong to an # IAM user, then that user is the implied owner. attr_reader :user - # @param [String] certificate_body The contents of the signing + # @param [String] certificate_body The contents of the signing # certificate. # @return [SigningCertificate] Returns the newly created signing # certificate. @@ -67,7 +67,7 @@ def upload certificate_body resp = client.upload_signing_certificate(options) - SigningCertificate.new_from(:upload_signing_certificate, + SigningCertificate.new_from(:upload_signing_certificate, resp.certificate, resp.certificate.certificate_id, new_options) end @@ -97,9 +97,9 @@ def clear # @param [Hash] options # @option options [Integer] :limit The maximum number of certificates # to yield. - # @option options [Integer] :batch_size The maximum number of + # @option options [Integer] :batch_size The maximum number of # certificates received each service reqeust. - # @yieldparam [SigningCertificate] signing_certificate + # @yieldparam [SigningCertificate] signing_certificate # @return [nil] def each options = {}, &block each_options = options.dup @@ -112,7 +112,7 @@ def each options = {}, &block def each_item response, &block response.certificates.each do |item| - cert = SigningCertificate.new_from(:list_signing_certificates, + cert = SigningCertificate.new_from(:list_signing_certificates, item, item.certificate_id, new_options) yield(cert) diff --git a/lib/aws/iam/user.rb b/lib/aws/iam/user.rb index 7ea2e800a3a..1c00b4f8377 100644 --- a/lib/aws/iam/user.rb +++ b/lib/aws/iam/user.rb @@ -14,7 +14,7 @@ module AWS class IAM - + # Represents an IAM User. Each AWS account can have many users. Users # can be organized (optionally) into groups. Users (and groups) can be # given policies that affect that they can do. @@ -24,7 +24,7 @@ class IAM # iam = AWS::IAM.new # # user = iam.users.create('johndoe') - # + # # # == Renaming a User # @@ -35,9 +35,9 @@ class IAM # user.name = 'newname' # # == User Path - # + # # When you create a user you can assign a path. Paths must begin and - # end with a forward slash (/). + # end with a forward slash (/). # # user = iam.users.create('newuser', :path => '/developers/ruby/') # @@ -91,8 +91,8 @@ def initialize name, options = {} # @attr_reader [String] The user's ARN (Amazon Resource Name). attribute :arn - # @attr [String] The path for this user. Paths are used to - # identify which division or part of an organization the user + # @attr [String] The path for this user. Paths are used to + # identify which division or part of an organization the user # belongs to. mutable_attribute :path @@ -136,7 +136,7 @@ def delete! # @return [PolicyCollection] Returns a collection that represents # all policies for this user. def policies - UserPolicyCollection.new(self) + UserPolicyCollection.new(self) end # Returns a collection that represents the signing certificates @@ -148,14 +148,14 @@ def policies # # If you need to access the signing certificates of this AWS account, # see {IAM#signing_certificates}. - # + # # @return [SigningCertificateCollection] Returns a collection that # represents signing certificates for this user. def signing_certificates SigningCertificateCollection.new(:user => self, :config => config) end - # @return [MFADeviceCollection] Returns a collection that represents + # @return [MFADeviceCollection] Returns a collection that represents # all MFA devices assigned to this user. def mfa_devices MFADeviceCollection.new(self) diff --git a/lib/aws/iam/user_collection.rb b/lib/aws/iam/user_collection.rb index aa206a656a6..0aa23051924 100644 --- a/lib/aws/iam/user_collection.rb +++ b/lib/aws/iam/user_collection.rb @@ -14,7 +14,7 @@ module AWS class IAM - # A collection that provides access to IAM users belonging to this + # A collection that provides access to IAM users belonging to this # account. # # iam = AWS::IAM.new @@ -58,8 +58,8 @@ class UserCollection include Collection::WithPrefix # @param [String] name Name of the user to create. - # @option options [String] :path ('/') The path for the user name. - # For more information about paths, see + # @option options [String] :path ('/') The path for the user name. + # For more information about paths, see # {Identifiers for IAM Entities}[http://docs.amazonwebservices.com/IAM/latest/UserGuide/index.html?Using_Identifiers.html] # @return [User] Returns the newly created user. def create name, options = {} @@ -67,7 +67,7 @@ def create name, options = {} create_opts[:user_name] = name create_opts[:path] = options[:path] if options[:path] resp = client.create_user(create_opts) - User.new_from(:create_user, resp.user, + User.new_from(:create_user, resp.user, resp.user.user_name, :config => config) end @@ -120,7 +120,7 @@ def enumerator options = {} def each_item response, &block response.users.each do |item| - user = User.new_from(:list_users, item, + user = User.new_from(:list_users, item, item.user_name, :config => config) yield(user) diff --git a/lib/aws/iam/virtual_mfa_device.rb b/lib/aws/iam/virtual_mfa_device.rb index c1a8ad07633..9ac4145fbc4 100644 --- a/lib/aws/iam/virtual_mfa_device.rb +++ b/lib/aws/iam/virtual_mfa_device.rb @@ -16,14 +16,14 @@ module AWS class IAM - # @attr_reader [String] base_32_string_seed The Base32 seed defined as - # specified in RFC3548. Only accessible on newly created + # @attr_reader [String] base_32_string_seed The Base32 seed defined as + # specified in RFC3548. Only accessible on newly created # devices. This value is Base64-encoded. - # - # @attr_reader [Blob] qr_code_png A QR code PNG image that encodes - # otpauth://totp/$virtualMFADeviceName@$AccountName? secret=$Base32String - # where $virtualMFADeviceName is one of the create call arguments, - # AccountName is the user name if set (accountId otherwise), and + # + # @attr_reader [Blob] qr_code_png A QR code PNG image that encodes + # otpauth://totp/$virtualMFADeviceName@$AccountName? secret=$Base32String + # where $virtualMFADeviceName is one of the create call arguments, + # AccountName is the user name if set (accountId otherwise), and # Base32String is the seed in Base32 format. Only accessible on newly # created devices. This value is Base64-encoded. # @@ -61,13 +61,13 @@ def user end end - # Enables the MFA device and associates it with the specified user. - # When enabled, the MFA device is required for every subsequent login + # Enables the MFA device and associates it with the specified user. + # When enabled, the MFA device is required for every subsequent login # by the user name associated with the device. # @param [User,String] user The user (or user name string) you want # to enable this device for. # @param [String] code1 An authentication code emitted by the device. - # @param [String] code2 A subsequent authentication code emitted by + # @param [String] code2 A subsequent authentication code emitted by # the device. def enable user, code1, code2 @@ -89,7 +89,7 @@ def enabled? !!enable_date end - # Deactivates the MFA device and removes it from association with + # Deactivates the MFA device and removes it from association with # the user for which it was originally enabled. # @return [nil] def deactivate @@ -110,7 +110,7 @@ def delete populates_from :create_virtual_mfa_device do |resp| if resp.virtual_mfa_device.serial_number == serial_number - resp.virtual_mfa_device + resp.virtual_mfa_device end end diff --git a/lib/aws/iam/virtual_mfa_device_collection.rb b/lib/aws/iam/virtual_mfa_device_collection.rb index e0825bdd13d..08134877d59 100644 --- a/lib/aws/iam/virtual_mfa_device_collection.rb +++ b/lib/aws/iam/virtual_mfa_device_collection.rb @@ -18,10 +18,10 @@ class VirtualMfaDeviceCollection include Collection - # Creates a new virtual MFA device for the AWS account. + # Creates a new virtual MFA device for the AWS account. # After creating the virtual MFA, you can enable the device to an # IAM user. - # + # # @param [String] name The name of the virtual MFA device. Name and path # together uniquely identify a virtual MFA device. # @param [Hash] options @@ -35,14 +35,14 @@ def create name, options = {} VirtualMfaDevice.new_from( :create_virtual_mfa_device, - resp.virtual_mfa_device, + resp.virtual_mfa_device, resp.virtual_mfa_device.serial_number, :config => config) end # Returns a virtual MFA device with the given serial number. - # @param [String] serial_number The serial number (ARN) of a virtual + # @param [String] serial_number The serial number (ARN) of a virtual # MFA device. # @return [VirtualMfaDevice] def [] serial_number diff --git a/lib/aws/record.rb b/lib/aws/record.rb index 4b175287d6d..4f220c89a87 100644 --- a/lib/aws/record.rb +++ b/lib/aws/record.rb @@ -15,7 +15,7 @@ module AWS - # AWS::Record is an ORM built on top of AWS services. + # AWS::Record is an ORM built on top of AWS services. module Record autoload :AbstractBase, 'aws/record/abstract_base' @@ -66,7 +66,7 @@ class RecordNotFound < StandardError; end # p.shard #=> 'products' # p.save # the product is persisted to the 'production-products' domain # - # @param [String] prefix A prefix to append to all domains. This is useful + # @param [String] prefix A prefix to append to all domains. This is useful # for grouping domains used by one application with a single prefix. # def self.domain_prefix= prefix @@ -105,7 +105,7 @@ def self.table_prefix @table_prefix end - # A utility method for casting values into an array. + # A utility method for casting values into an array. # # * nil is returned as an empty array, [] # * Arrays are returned unmodified @@ -123,7 +123,7 @@ def self.as_array value end end - # A utility method for casting values into + # A utility method for casting values into # # * Sets are returned unmodified # * everything else is passed through #{as_array} and then into a new Set diff --git a/lib/aws/record/attributes.rb b/lib/aws/record/attributes.rb index 9f51ae1b482..738d21185c9 100644 --- a/lib/aws/record/attributes.rb +++ b/lib/aws/record/attributes.rb @@ -19,15 +19,15 @@ module Attributes # Base class for all of the AWS::Record attributes. class BaseAttr - + # @param [Symbol] name Name of this attribute. It should be a name that # is safe to use as a method. # @param [Hash] options - # @option options [String] :persist_as Defaults to the name of the + # @option options [String] :persist_as Defaults to the name of the # attribute. You can pass a string to specify what the attribute # will be named in the backend storage. # @option options [Boolean] :set (false) When true this attribute can - # accept multiple unique values. + # accept multiple unique values. def initialize name, options = {} @name = name.to_s @options = options.dup @@ -35,62 +35,62 @@ def initialize name, options = {} raise ArgumentError, "invalid option :set for #{self.class}" end end - + # @return [String] The name of this attribute attr_reader :name - + # @return [Hash] Attribute options passed to the constructor. attr_reader :options - - # @return [Boolean] Returns true if this attribute can have + + # @return [Boolean] Returns true if this attribute can have # multiple values. def set? options[:set] ? true : false end - + # @return Returns the default value for this attribute. def default_value options[:default_value] end - # @return [String] Returns the name this attribute will use + # @return [String] Returns the name this attribute will use # in the storage backend. def persist_as (options[:persist_as] || @name).to_s end - + # @param [Mixed] raw_value A single value to type cast. # @return [Mixed] Returns the type casted value. def type_cast raw_value self.class.type_cast(raw_value, options) end - + # @param [String] serialized_value The serialized string value. # @return [Mixed] Returns a deserialized type-casted value. def deserialize serialized_value self.class.deserialize(serialized_value, options) end - + # Takes the type casted value and serializes it # @param [Mixed] type_casted_value A single value to serialize. # @return [Mixed] Returns the serialized value. def serialize type_casted_value self.class.serialize(type_casted_value, options) end - + # @param [String] serialized_value The raw value returned from AWS. # @return [Mixed] Returns the type-casted deserialized value. def self.deserialize serialized_value, options = {} self.type_cast(serialized_value, options) end - + # @return [Boolean] Returns true if this attribute type can be used - # with the +:set => true+ option. Certain attirbutes can not + # with the +:set => true+ option. Certain attirbutes can not # be represented with multiple values (like BooleanAttr). def self.allow_set? raise NotImplementedError end - + # @private protected def self.expect klass, value, &block @@ -103,8 +103,8 @@ def self.expect klass, value, &block end class StringAttr < BaseAttr - - # Returns the value cast to a string. Empty strings are returned as + + # Returns the value cast to a string. Empty strings are returned as # nil by default. Type casting is done by calling #to_s on the value. # # string_attr.type_cast(123) @@ -119,7 +119,7 @@ class StringAttr < BaseAttr # @param [Mixed] raw_value # @param [Hash] options # @option options [Boolean] :preserve_empty_strings (false) When true, - # empty strings are preserved and not cast to nil. + # empty strings are preserved and not cast to nil. # @return [String,nil] The type casted value. def self.type_cast raw_value, options = {} case raw_value @@ -129,7 +129,7 @@ def self.type_cast raw_value, options = {} else raw_value.to_s end end - + # Returns a serialized representation of the string value suitable for # storing in SimpleDB. # @param [String] string @@ -138,16 +138,16 @@ def self.type_cast raw_value, options = {} def self.serialize string, options = {} unless string.is_a?(String) msg = "expected a String value, got #{string.class}" - raise ArgumentError, msg + raise ArgumentError, msg end string end - + # @private def self.allow_set? true end - + end class BooleanAttr < BaseAttr @@ -165,7 +165,7 @@ def self.serialize boolean, options = {} case boolean when false then 0 when true then 1 - else + else msg = "expected a boolean value, got #{boolean.class}" raise ArgumentError, msg end @@ -179,8 +179,8 @@ def self.allow_set? end class IntegerAttr < BaseAttr - - # Returns value cast to an integer. Empty strings are cast to + + # Returns value cast to an integer. Empty strings are cast to # nil by default. Type casting is done by calling #to_i on the value. # # int_attribute.type_cast('123') @@ -197,12 +197,12 @@ def self.type_cast raw_value, options = {} when '' then nil when Integer then raw_value else - raw_value.respond_to?(:to_i) ? + raw_value.respond_to?(:to_i) ? raw_value.to_i : raw_value.to_s.to_i end end - + # Returns a serialized representation of the integer value suitable for # storing in SimpleDB. # @@ -215,7 +215,7 @@ def self.type_cast raw_value, options = {} def self.serialize integer, options = {} expect(Integer, integer) { integer } end - + # @private def self.allow_set? true @@ -224,14 +224,14 @@ def self.allow_set? end class FloatAttr < BaseAttr - + def self.type_cast raw_value, options = {} case raw_value when nil then nil when '' then nil when Float then raw_value else - raw_value.respond_to?(:to_f) ? + raw_value.respond_to?(:to_f) ? raw_value.to_f : raw_value.to_s.to_f end @@ -250,8 +250,8 @@ def self.allow_set? class DateAttr < BaseAttr - # Returns value cast to a Date object. Empty strings are cast to - # nil. Values are cast first to strings and then passed to + # Returns value cast to a Date object. Empty strings are cast to + # nil. Values are cast first to strings and then passed to # Date.parse. Integers are treated as timestamps. # # date_attribute.type_cast('2000-01-02T10:11:12Z') @@ -274,13 +274,13 @@ def self.type_cast raw_value, options = {} when nil then nil when '' then nil when Date then raw_value - when Integer then + when Integer then begin Date.parse(Time.at(raw_value).to_s) # assumed timestamp rescue nil end - else + else begin Date.parse(raw_value.to_s) # Time, DateTime or String rescue @@ -288,7 +288,7 @@ def self.type_cast raw_value, options = {} end end end - + # Returns a Date object encoded as a string (suitable for sorting). # # attribute.serialize(DateTime.parse('2001-01-01')) @@ -307,18 +307,18 @@ def self.serialize date, options = {} end date.strftime('%Y-%m-%d') end - + # @private def self.allow_set? true end - + end class DateTimeAttr < BaseAttr - - # Returns value cast to a DateTime object. Empty strings are cast to - # nil. Values are cast first to strings and then passed to + + # Returns value cast to a DateTime object. Empty strings are cast to + # nil. Values are cast first to strings and then passed to # DateTime.parse. Integers are treated as timestamps. # # datetime_attribute.type_cast('2000-01-02') @@ -341,13 +341,13 @@ def self.type_cast raw_value, options = {} when nil then nil when '' then nil when DateTime then raw_value - when Integer then + when Integer then begin DateTime.parse(Time.at(raw_value).to_s) # timestamp rescue nil end - else + else begin DateTime.parse(raw_value.to_s) # Time, Date or String rescue @@ -355,7 +355,7 @@ def self.type_cast raw_value, options = {} end end end - + # Returns a DateTime object encoded as a string (suitable for sorting). # # attribute.serialize(DateTime.parse('2001-01-01')) @@ -368,16 +368,16 @@ def self.type_cast raw_value, options = {} def self.serialize datetime, options = {} unless datetime.is_a?(DateTime) msg = "expected a DateTime value, got #{datetime.class}" - raise ArgumentError, msg + raise ArgumentError, msg end datetime.strftime('%Y-%m-%dT%H:%M:%S%Z') end - + # @private def self.allow_set? true end - + end end end diff --git a/lib/aws/record/conversion.rb b/lib/aws/record/conversion.rb index e5d8c49142d..73b98017d92 100644 --- a/lib/aws/record/conversion.rb +++ b/lib/aws/record/conversion.rb @@ -31,7 +31,7 @@ def to_key def to_param persisted? ? to_key.join('-') : nil end - + end end diff --git a/lib/aws/record/dirty_tracking.rb b/lib/aws/record/dirty_tracking.rb index bec5d905ee6..78d3f618a75 100644 --- a/lib/aws/record/dirty_tracking.rb +++ b/lib/aws/record/dirty_tracking.rb @@ -28,9 +28,9 @@ module Record # my_book.changed #=> ['title'] # # Or you can get a more detailed description of the changes. {#changes} - # returns a hash of changed attributes (keys) with their old and new + # returns a hash of changed attributes (keys) with their old and new # values. - # + # # my_book.changes # #=> { 'title' => ['My Book', 'My Awesome Book'] # @@ -39,7 +39,7 @@ module Record # attribute: # # string_attr :title - # + # # You can now call any of the following methods: # # * title_changed? @@ -49,7 +49,7 @@ module Record # * title_will_change! # # Given the title change from above: - # + # # my_book.title_changed? #=> true # my_book.title_change #=> ['My Book', 'My Awesome Book'] # my_book.title_was #=> ['My Book'] @@ -59,7 +59,7 @@ module Record # # == In-Place Editing # - # Dirty tracking works by comparing incoming attribute values upon + # Dirty tracking works by comparing incoming attribute values upon # assignment against the value that was there previously. If you # use functions against the value that modify it (like gsub!) # you must notify your record about the coming change. @@ -72,11 +72,11 @@ module Record # == Partial Updates # # Dirty tracking makes it possible to only persist those attributes - # that have changed since they were loaded. This speeds up requests + # that have changed since they were loaded. This speeds up requests # against AWS when saving data. # module DirtyTracking - + # Returns true if this model has unsaved changes. # # b = Book.new(:title => 'My Book') @@ -96,19 +96,19 @@ module DirtyTracking def changed? !orig_values.empty? end - - # Returns an array of attribute names that have changes. + + # Returns an array of attribute names that have changes. # # book.changed #=> [] # person.title = 'New Title' # book.changed #=> ['title'] # - # @return [Array] Returns an array of attribute names that have + # @return [Array] Returns an array of attribute names that have # unsaved changes. def changed orig_values.keys end - + # Returns the changed attributes in a hash. Keys are attribute names, # values are two value arrays. The first value is the previous # attribute value, the second is the current attribute value. @@ -124,16 +124,16 @@ def changes changes end end - + # Returns true if the named attribute has unsaved changes. # - # This is an attribute method. The following two expressions + # This is an attribute method. The following two expressions # are equivilent: # # book.title_changed? # book.attribute_changed?(:title) # - # @param [String] attribute_name Name of the attribute to check + # @param [String] attribute_name Name of the attribute to check # for changes. # # @return [Boolean] Returns true if the named attribute @@ -143,11 +143,11 @@ def changes def attribute_changed? attribute_name orig_values.keys.include?(attribute_name) end - - # Returns an array of the old value and the new value for + + # Returns an array of the old value and the new value for # attributes that have unsaved changes, returns nil otherwise. # - # This is an attribute method. The following two expressions + # This is an attribute method. The following two expressions # are equivilent: # # book.title_change @@ -168,7 +168,7 @@ def attribute_changed? attribute_name # book = Book.first # book.title = 'New Title' # book.title_change #=> ['Old Title', 'New Title'] - # + # # @param [String] attribute_name Name of the attribute to fetch # a change for. # @return [Boolean] Returns true if the named attribute @@ -184,11 +184,11 @@ def attribute_change attribute_name end end end - + # Returns the previous value for changed attributes, or the current # value for unchanged attributes. # - # This is an attribute method. The following two expressions + # This is an attribute method. The following two expressions # are equivilent: # # book.title_was @@ -215,7 +215,7 @@ def attribute_was attribute_name orig_values.has_key?(name) ? orig_values[name] : __send__(name) end end - + # Reverts any changes to the attribute, restoring its original value. # @param [String] attribute_name Name of the attribute to reset. # @return [nil] @@ -225,7 +225,7 @@ def reset_attribute! attribute_name __send__("#{attribute_name}=", attribute_was(attribute_name)) nil end - + # Indicate to the record that you are about to edit an attribute # in place. # @param [String] attribute_name Name of the attribute that will @@ -240,7 +240,7 @@ def attribute_will_change! attribute_name was = __send__(name) begin # booleans, nil, etc all #respond_to?(:clone), but they raise - # a TypeError when you attempt to dup them. + # a TypeError when you attempt to dup them. orig_values[name] = was.clone rescue TypeError orig_values[name] = was @@ -254,12 +254,12 @@ def attribute_will_change! attribute_name def orig_values @_orig_values ||= {} end - + private def clear_change! attribute_name orig_values.delete(attribute_name) end - + private def ignore_changes &block begin @@ -269,17 +269,17 @@ def ignore_changes &block @_ignore_changes = false end end - + private def if_tracking_changes &block yield unless @_ignore_changes end - + private def clear_changes! orig_values.clear end - + end end end diff --git a/lib/aws/record/errors.rb b/lib/aws/record/errors.rb index 495327f9731..80517dc8a06 100644 --- a/lib/aws/record/errors.rb +++ b/lib/aws/record/errors.rb @@ -15,7 +15,7 @@ module AWS module Record class Errors < Core::IndifferentHash - + include Enumerable # Returns the errors for the atttibute in an array. @@ -41,11 +41,11 @@ def [] attribute_name # errors.on(:name) # #=> ['may not be blank'] # - # If you want to add a general error message, then pass +:base+ + # If you want to add a general error message, then pass +:base+ # for +attribute_name+, or call {#add_to_base}. # @param [String,Symbol] attribute_name The name of the attribute # that you are adding an error to. - # @param [String] message ('is invalid') The error message (should + # @param [String] message ('is invalid') The error message (should # not contain the attribute name). # @return [String] Returns the message. def []= attribute_name, message = 'is invalid' @@ -60,7 +60,7 @@ def []= attribute_name, message = 'is invalid' # Adds a general error message (not associated with any particular # attribute). - # @param [String] message ('is invalid') The error message (should + # @param [String] message ('is invalid') The error message (should # not contain the attribute name). # @return [String] Returns the message. def add_to_base message @@ -73,9 +73,9 @@ def count end alias_method :size, :count - # Yields once for each error message added. + # Yields once for each error message added. # - # An attribute_name may yield more than once if there are more than + # An attribute_name may yield more than once if there are more than # one errors associated with that attirbute. # # @yield [attribute_name, error_message] @@ -94,7 +94,7 @@ def each &block # name. # # errors.add(:name, 'may not be blank') - # errors.full_messages + # errors.full_messages # #=> ['Name may not be blank'] # # @return [Array of Strings] Returns an array of error messages. diff --git a/lib/aws/record/exceptions.rb b/lib/aws/record/exceptions.rb index a10f3daa8d4..475bcc5f103 100644 --- a/lib/aws/record/exceptions.rb +++ b/lib/aws/record/exceptions.rb @@ -13,7 +13,7 @@ module AWS module Record - + # Raised when trying to access an attribute that does not exist. # @private class UndefinedAttributeError < StandardError @@ -21,7 +21,7 @@ def initalize attribute_name super("undefined attribute `#{attribute_name}`") end end - + # Raised when calling #save!, #create! or #update_attributes! on a record that # has validation errors. # @private diff --git a/lib/aws/record/hash_model.rb b/lib/aws/record/hash_model.rb index 2cf1f31e724..2298bc4d09e 100644 --- a/lib/aws/record/hash_model.rb +++ b/lib/aws/record/hash_model.rb @@ -72,9 +72,9 @@ def create_table read_capacity_units, write_capacity_units, options = {} create_opts[:hash_key] = { :id => :string } dynamo_db.tables.create( - table_name, - read_capacity_units, - write_capacity_units, + table_name, + read_capacity_units, + write_capacity_units, create_opts) end @@ -99,7 +99,7 @@ def dynamo_db end - # @return [DynamoDB::Item] Returns a reference to the item as stored in + # @return [DynamoDB::Item] Returns a reference to the item as stored in # simple db. # @private private diff --git a/lib/aws/record/hash_model/finder_methods.rb b/lib/aws/record/hash_model/finder_methods.rb index cb91d72acae..ff9ff2e490b 100644 --- a/lib/aws/record/hash_model/finder_methods.rb +++ b/lib/aws/record/hash_model/finder_methods.rb @@ -24,21 +24,21 @@ class << self # was no data found for the given id. # @return [Record::HashModel] Returns the record with the given id. def find_by_id id, options = {} - + table = dynamo_db_table(options[:shard]) data = table.items[id].attributes.to_h - + raise RecordNotFound, "no data found for id: #{id}" if data.empty? - + obj = self.new(:shard => table) obj.send(:hydrate, id, data) obj - + end alias_method :[], :find_by_id - - # Finds records in Amazon DynamoDB and returns them as objects of + + # Finds records in Amazon DynamoDB and returns them as objects of # the current class. # # Finding +:all+ returns an enumerable scope object @@ -70,13 +70,13 @@ def find_by_id id, options = {} # and array is returned of records. When finding +:first+ then # +nil+ or a single record will be returned. # @param [Hash] options - # @option options [Integer] :shard The shard name of the Amazon + # @option options [Integer] :shard The shard name of the Amazon # DynamoDB table to search. # @option options [Integer] :limit The max number of records to fetch. def find *args new_scope.find(*args) end - + # Returns a chainable scope object that restricts further scopes to a # particular table. # @@ -90,7 +90,7 @@ def shard shard_name new_scope.shard(shard_name) end alias_method :domain, :shard # backwards compat - + # Returns an enumerable scope object represents all records. # # Book.all.each do |book| @@ -114,7 +114,7 @@ def all options = {} def each &block all.each(&block) end - + # Counts records Amazon DynamoDB. # # class Product < AWS::Record::HashModel @@ -139,23 +139,23 @@ def each &block # @param [Hash] options # # @option [String] :shard Which shard to count records in. - # + # # @return [Integer] The count of records in the table. # def count options = {} new_scope.count(options) end alias_method :size, :count - + # @return [Object,nil] Returns the first record found. If there were # no records found, nil is returned. def first options = {} new_scope.first(options) end - + # The maximum number of records to return. By default, all records # matching the where conditions will be returned from a find. - # + # # People.limit(10).each {|person| ... } # # Limit can be chained with other scope modifiers: diff --git a/lib/aws/record/hash_model/scope.rb b/lib/aws/record/hash_model/scope.rb index f306fc7ed61..abd6e149492 100644 --- a/lib/aws/record/hash_model/scope.rb +++ b/lib/aws/record/hash_model/scope.rb @@ -10,7 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. - + module AWS module Record class HashModel @@ -20,7 +20,7 @@ class HashModel # == Getting a Scope Object # # You should normally never need to construct a Scope object directly. - # Scope objects are returned from the AWS::Record::HashModel finder + # Scope objects are returned from the AWS::Record::HashModel finder # methods # (e.g. +shard+ and +limit+). # # books = Book.limit(100) @@ -63,20 +63,20 @@ class HashModel # Book.shard('books-1').first # class Scope < Record::Scope - + private def _each_object &block - + items = _item_collection - + items.select(:limit => @options[:limit]).each do |item_data| obj = base_class.new(:shard => _shard) obj.send(:hydrate, item_data.attributes['id'], item_data.attributes) yield(obj) end - + end - + private def _merge_scope scope merged = self @@ -87,7 +87,7 @@ def _merge_scope scope end merged end - + private def _handle_options options scope = self @@ -96,12 +96,12 @@ def _handle_options options end scope end - + private def _item_collection base_class.dynamo_db_table(_shard).items end - + end end end diff --git a/lib/aws/record/model.rb b/lib/aws/record/model.rb index cc41a2c1e1a..979ea35c9e6 100644 --- a/lib/aws/record/model.rb +++ b/lib/aws/record/model.rb @@ -37,7 +37,7 @@ module Record # When extending AWS::Record::Model you should first consider what # attributes your class should have. Unlike ActiveRecord, AWS::Record # models are not backed by a database table/schema. You must choose what - # attributes (and what types) you need. + # attributes (and what types) you need. # # * +string_attr+ # * +boolean_attr+ @@ -46,7 +46,7 @@ module Record # * +datetime_attr+ # * +date_attr+ # - # === Usage + # === Usage # # Normally you just call these methods inside your model class definition: # @@ -104,9 +104,9 @@ module Record # * values are unordered # * duplicate values are automatically omitted # - # Please consider these limitations when you choose to use the +:set+ + # Please consider these limitations when you choose to use the +:set+ # option with the attribute macros. - # + # # = Validations # # It's important to validate models before there are persisted to keep @@ -127,7 +127,7 @@ module Record # # For more information about the available validation methods see # {Validations}. - # + # # = Finder Methods # # You can find records by their ID. Each record gets a UUID when it @@ -154,7 +154,7 @@ module Record # end # # Be careful when enumerating all. Depending on the number of records - # and number of attributes each record has, this can take a while, + # and number of attributes each record has, this can take a while, # causing quite a few requests. # # === First @@ -173,7 +173,7 @@ module Record # book = Book.first(:where => { :has_been_read => false }) # # You can pass as find options: - # + # # * +:where+ - Conditions that must be met to be returned # * +:order+ - The order to sort matched records by # * +:limit+ - The maximum number of records to return @@ -186,7 +186,7 @@ module Record # class Book < AWS::Record::Model # # scope :mine, where(:owner => 'Me') - # + # # scope :unread, where(:has_been_read => false) # # scope :by_popularity, order(:score, :desc) @@ -197,7 +197,7 @@ module Record # # # The following expression returns 10 books that belong # # to me, that are unread sorted by popularity. - # next_good_reads = Book.mine.unread.top_10 + # next_good_reads = Book.mine.unread.top_10 # # There are 3 standard scope methods: # @@ -209,7 +209,7 @@ module Record # # Where accepts aruments in a number of forms: # - # 1. As an sql-like fragment. If you need to escape values this form is + # 1. As an sql-like fragment. If you need to escape values this form is # not suggested. # # Book.where('title = "My Book"') @@ -219,8 +219,8 @@ module Record # # Book.where('title = ?', 'My Book') # - # 3. A hash of key-value pairs. This is the simplest form, but also the - # least flexible. You can not use this form if you need more complex + # 3. A hash of key-value pairs. This is the simplest form, but also the + # least flexible. You can not use this form if you need more complex # expressions that use or. # # Book.where(:title => 'My Book') @@ -230,10 +230,10 @@ module Record # This orders the records as returned by AWS. Default ordering is ascending. # Pass the value :desc as a second argument to sort in reverse ordering. # - # Book.order(:title) # alphabetical ordering - # Book.order(:title, :desc) # reverse alphabetical ordering + # Book.order(:title) # alphabetical ordering + # Book.order(:title, :desc) # reverse alphabetical ordering # - # You may only order by a single attribute. If you call order twice in the + # You may only order by a single attribute. If you call order twice in the # chain, the last call gets presedence: # # Book.order(:title).order(:price) @@ -332,7 +332,7 @@ def sdb end - # @return [SimpleDB::Item] Returns a reference to the item as stored in + # @return [SimpleDB::Item] Returns a reference to the item as stored in # simple db. # @private private diff --git a/lib/aws/record/model/attributes.rb b/lib/aws/record/model/attributes.rb index 6354c25b04e..0d3b5cc557f 100644 --- a/lib/aws/record/model/attributes.rb +++ b/lib/aws/record/model/attributes.rb @@ -44,7 +44,7 @@ def initialize name, options = {} range.is_a?(Range) and range.first.is_a?(Integer) super(name, options) end - + # Returns a serialized representation of the integer value suitable for # storing in SimpleDB. # @@ -61,7 +61,7 @@ def initialize name, options = {} # # @param [Integer] integer The number to serialize. # @param [Hash] options - # @option options [required,Range] :range A range that represents the + # @option options [required,Range] :range A range that represents the # minimum and maximum values this integer can be. # The returned value will have an offset applied (if min is # less than 0) and will be zero padded. @@ -115,7 +115,7 @@ def initialize name, options = {} end def self.serialize float, options = {} - expect(Float, float) do + expect(Float, float) do left, right = float.to_s.split('.') left = SortableIntegerAttr.serialize(left.to_i, options) SortableIntegerAttr.check_range(float, options) @@ -136,7 +136,7 @@ def self.deserialize string_value, options = {} end class << self - + # Adds a string attribute to this class. # # @example A standard string attribute @@ -155,7 +155,7 @@ class << self # end # # recipe = Recipe.new(:tags => %w(popular dessert)) - # recipe.tags #=> # + # recipe.tags #=> # # # @param [Symbol] name The name of the attribute. # @param [Hash] options @@ -164,7 +164,7 @@ class << self def string_attr name, options = {} add_attribute(Record::Attributes::StringAttr.new(name, options)) end - + # Adds an integer attribute to this class. # # class Recipe < AWS::Record::Model @@ -181,7 +181,7 @@ def string_attr name, options = {} def integer_attr name, options = {} add_attribute(Attributes::IntegerAttr.new(name, options)) end - + # Adds a sortable integer attribute to this class. # # class Person < AWS::Record::Model @@ -192,7 +192,7 @@ def integer_attr name, options = {} # person.age #=> 10 # # === Validations - # + # # It is recomended to apply a validates_numericality_of with # minimum and maximum value constraints. If a value is assigned # to a sortable integer that falls outside of the +:range: it will @@ -214,7 +214,7 @@ def integer_attr name, options = {} def sortable_integer_attr name, options = {} add_attribute(Attributes::SortableIntegerAttr.new(name, options)) end - + # Adds a float attribute to this class. # # class Listing < AWS::Record::Model @@ -231,11 +231,11 @@ def sortable_integer_attr name, options = {} def float_attr name, options = {} add_attribute(Attributes::FloatAttr.new(name, options)) end - + # Adds sortable float attribute to this class. # # Persisted values are stored (and sorted) as strings. This makes it - # more difficult to sort numbers because they don't sort + # more difficult to sort numbers because they don't sort # lexicographically unless they have been offset to be positive and # then zero padded. # @@ -245,7 +245,7 @@ def float_attr name, options = {} # # sortable_float_attr :score, :range => (0..10) # - # This will cause values like 5.5 to persist as a string like '05.5' so + # This will cause values like 5.5 to persist as a string like '05.5' so # that they can be sorted lexicographically. # # === Negative Floats @@ -255,8 +255,8 @@ def float_attr name, options = {} # # sortable_float_attr :position, :range => (-10..10) # - # AWS::Record will add 10 to all values and zero pad them - # (e.g. -10.0 will be represented as '00.0' and 10 will be represented as + # AWS::Record will add 10 to all values and zero pad them + # (e.g. -10.0 will be represented as '00.0' and 10 will be represented as # '20.0'). This will allow the values to be compared lexicographically. # # @note If you change the +:range+ after some values have been persisted @@ -274,7 +274,7 @@ def float_attr name, options = {} def sortable_float_attr name, options = {} add_attribute(Attributes::SortableFloatAttr.new(name, options)) end - + # Adds a boolean attribute to this class. # # @example @@ -293,16 +293,16 @@ def sortable_float_attr name, options = {} # # @param [Symbol] name The name of the attribute. def boolean_attr name, options = {} - + attr = add_attribute(Attributes::BooleanAttr.new(name, options)) - + # add the boolean question mark method define_method("#{attr.name}?") do !!__send__(attr.name) end - + end - + # Adds a datetime attribute to this class. # # @example A standard datetime attribute @@ -327,7 +327,7 @@ def boolean_attr name, options = {} def datetime_attr name, options = {} add_attribute(Record::Attributes::DateTimeAttr.new(name, options)) end - + # Adds a date attribute to this class. # # @example A standard date attribute @@ -350,7 +350,7 @@ def datetime_attr name, options = {} def date_attr name, options = {} add_attribute(Record::Attributes::DateAttr.new(name, options)) end - + # A convenience method for adding the standard two datetime attributes # +:created_at+ and +:updated_at+. # @@ -364,13 +364,13 @@ def date_attr name, options = {} # recipe.save # recipe.created_at #=> # recipe.updated_at #=> - # + # def timestamps c = datetime_attr :created_at u = datetime_attr :updated_at [c, u] end - + end end end diff --git a/lib/aws/record/model/finder_methods.rb b/lib/aws/record/model/finder_methods.rb index 0ea4daa37df..f6cebdac3df 100644 --- a/lib/aws/record/model/finder_methods.rb +++ b/lib/aws/record/model/finder_methods.rb @@ -24,21 +24,21 @@ class << self # was no data found for the given id. # @return [Record::HashModel] Returns the record with the given id. def find_by_id id, options = {} - + domain = sdb_domain(options[:shard] || options[:domain]) - + data = domain.items[id].data.attributes - + raise RecordNotFound, "no data found for id: #{id}" if data.empty? - + obj = self.new(:shard => domain) obj.send(:hydrate, id, data) obj - + end alias_method :[], :find_by_id - - # Finds records in SimpleDB and returns them as objects of the + + # Finds records in SimpleDB and returns them as objects of the # current class. # # Finding +:all+ returns an enumerable scope object @@ -79,13 +79,13 @@ def find_by_id id, options = {} # @param [Hash] options # @option options [Mixed] :where Conditions that determine what # records are returned. - # @option options [String,Array] :sort The order records should be + # @option options [String,Array] :sort The order records should be # returned in. # @option options [Integer] :limit The max number of records to fetch. def find *args new_scope.find(*args) end - + # Returns a chainable scope object that restricts further scopes to a # particular domain. # @@ -99,7 +99,7 @@ def shard shard_name new_scope.shard(shard_name) end alias_method :domain, :shard - + # Returns an enumerable scope object represents all records. # # Book.all.each do |book| @@ -107,7 +107,7 @@ def shard shard_name # end # # This method is equivalent to +find(:all)+, and therefore you can also - # pass aditional options. See {.find} for more information on what + # pass aditional options. See {.find} for more information on what # options you can pass. # # Book.all(:where => { :author' => 'me' }).each do |my_book| @@ -123,7 +123,7 @@ def all options = {} def each &block all.each(&block) end - + # Counts records in SimpleDB. # # With no arguments, counts all records: @@ -150,19 +150,19 @@ def count(options = {}) new_scope.count(options) end alias_method :size, :count - + # @return [Object,nil] Returns the first record found. If there were # no records found, nil is returned. def first options = {} new_scope.first(options) end - + # Limits which records are retried from SimpleDB when performing a find. - # + # # Simple string condition # # Car.where('color = "red" or color = "blue"').each {|car| ... } - # + # # String with placeholders for quoting params # # Car.where('color = ?', 'red') @@ -171,7 +171,7 @@ def first options = {} # # # produces a condition using in, like: WHERE color IN ('red', 'blue') # Car.where('color IN ?', ['red','blue']) - # + # # Hash arguments # # # WHERE age = '40' AND gender = 'male' @@ -184,9 +184,9 @@ def first options = {} # # # 10 most expensive red cars # Car.where(:color => 'red').order(:price, :desc).limit(10) - # + # # @overload where(conditions_hash) - # @param [Hash] conditions_hash A hash of attributes to values. Each + # @param [Hash] conditions_hash A hash of attributes to values. Each # key/value pair from the hash becomes a find condition. All # conditions are joined by AND. # @@ -195,7 +195,7 @@ def first options = {} def where *args new_scope.where(*args) end - + # Defines the order in which records are returned when performing a find. # SimpleDB only allows sorting by one attribute per request. # @@ -212,10 +212,10 @@ def where *args def order *args new_scope.order(*args) end - + # The maximum number of records to return. By default, all records # matching the where conditions will be returned from a find. - # + # # People.limit(10).each {|person| ... } # # Limit can be chained with other scope modifiers: diff --git a/lib/aws/record/model/scope.rb b/lib/aws/record/model/scope.rb index 24830130140..49f7aba2130 100644 --- a/lib/aws/record/model/scope.rb +++ b/lib/aws/record/model/scope.rb @@ -10,7 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. - + module AWS module Record class Model @@ -59,13 +59,13 @@ class Model # Book.where('author' => 'John Doe').first # class Scope < Record::Scope - + # @private def initialize base_class, options = {} super @options[:where] ||= [] end - + def new attributes = {} attributes = attributes.dup @@ -75,11 +75,11 @@ def new attributes = {} attributes.merge!(conditions.first) end end - + super(attributes) - + end - + # Applies conditions to the scope that limit which records are returned. # Only those matching all given conditions will be returned. # @@ -111,8 +111,8 @@ def where *conditions end _with(:where => @options[:where] + [conditions]) end - - # Specifies how to sort records returned. + + # Specifies how to sort records returned. # # # enumerate books, starting with the most recently published ones # Book.order(:published_at, :desc).each do |book| @@ -122,7 +122,7 @@ def where *conditions # Only one order may be applied. If order is specified more than # once the last one in the chain takes precedence: # - # + # # # books returned by this scope will be ordered by :published_at # # and not :author. # Book.where(:read => false).order(:author).order(:published_at) @@ -132,25 +132,25 @@ def where *conditions def order attribute_name, order = :asc _with(:order => [attribute_name, order]) end - + # @private private def _each_object &block - + items = _item_collection - + items.select.each do |item_data| obj = base_class.new(:shard => _shard) obj.send(:hydrate, item_data.name, item_data.attributes) yield(obj) end - + end - + # Merges another scope with this scope. Conditions are added together # and the limit and order parts replace those in this scope (if set). # @param [Scope] scope A scope to merge with this one. - # @return [Scope] Returns a new scope with merged conditions and + # @return [Scope] Returns a new scope with merged conditions and # overriden order and limit. # @private private @@ -159,8 +159,8 @@ def _merge_scope scope scope.instance_variable_get('@options').each_pair do |opt_name,opt_value| unless [nil, []].include?(opt_value) if opt_name == :where - opt_value.each do |condition| - merged = merged.where(*condition) + opt_value.each do |condition| + merged = merged.where(*condition) end else merged = merged.send(opt_name, *opt_value) @@ -169,14 +169,14 @@ def _merge_scope scope end merged end - + # Consumes a hash of options (e.g. +:where+, +:order+ and +:limit+) and # builds them onto the current scope, returning a new one. # @param [Hash] options # @option options :where # @option options :order # @option options [Integer] :limit - # @return [Scope] Returns a new scope with the hash of scope + # @return [Scope] Returns a new scope with the hash of scope # options applied. # @private private @@ -192,7 +192,7 @@ def _handle_options options end scope end - + # Converts this scope object into an AWS::SimpleDB::ItemCollection # @return [SimpleDB::ItemCollection] # @private @@ -206,7 +206,7 @@ def _item_collection end items end - + end end end diff --git a/lib/aws/record/naming.rb b/lib/aws/record/naming.rb index aec02b55f2b..afa6968b5b5 100644 --- a/lib/aws/record/naming.rb +++ b/lib/aws/record/naming.rb @@ -16,7 +16,7 @@ module Record # @private module Naming - + # This method should only ever get called in a Rails 3+ context # where active model and active support have been loaded. Rails 2 # does not call model name on object. diff --git a/lib/aws/record/scope.rb b/lib/aws/record/scope.rb index 689db4a9cc8..7f4ddf17c50 100644 --- a/lib/aws/record/scope.rb +++ b/lib/aws/record/scope.rb @@ -10,16 +10,16 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. - + module AWS module Record # Base class for {AWS::Record::Model::Scope} and # {AWS::Record::HashModel::Scope}. class Scope - + include Enumerable - + # @param base_class A class that extends {AWS::Record::AbstractBase}. # @param [Hash] options # @option options : @@ -34,7 +34,7 @@ def initialize base_class, options = {} @options[:shard] = @options.delete(:domain) if @options[:domain] end - + # @return [Class] Returns the AWS::Record::Model extending class that # this scope will find records for. attr_reader :base_class @@ -61,13 +61,13 @@ def shard shard_name _with(:shard => shard_name) end alias_method :domain, :shard - + # @overload find(id) # Finds and returns a single record by id. If no record is found # with the given +id+, then a RecordNotFound error will be raised. # @param [String] id ID of the record to find. # @return Returns the record. - # + # # @overload find(:first, options = {}) # Returns the first record found. If no records were matched then # nil will be returned (raises no exceptions). @@ -96,7 +96,7 @@ def find id_or_mode, options = {} else base_class.find_by_id(id_or_mode, :shard => scope._shard) end - + end # @return [Integer] Returns the number of records that match the @@ -115,7 +115,7 @@ def count options = {} def first options = {} _handle_options(options).find(:first) end - + # Limits the maximum number of total records to return when finding # or counting. Returns a scope, does not make a request. # @@ -126,7 +126,7 @@ def first options = {} def limit limit _with(:limit => limit) end - + # Yields once for each record matching the request made by this scope. # # books = Book.where(:author => 'me').order(:price, :asc).limit(10) @@ -135,7 +135,7 @@ def limit limit # puts book.attributes.to_yaml # end # - # @yieldparam [Object] record + # @yieldparam [Object] record def each &block if block_given? _each_object(&block) @@ -149,26 +149,26 @@ def _shard @options[:shard] || base_class.shard_name end alias_method :domain, :shard - + # @private private def _each_object &block raise NotImplementedError end - + # @private private def _with options self.class.new(base_class, @options.merge(options)) end - + # @private private def method_missing scope_name, *args # @todo only proxy named scope methods _merge_scope(base_class.send(scope_name, *args)) end - + # Merges the one scope with the current scope, returning a 3rd. # @param [Scope] scope # @return [Scope] diff --git a/lib/aws/record/validations.rb b/lib/aws/record/validations.rb index b215dfbe7a3..9052905b53b 100644 --- a/lib/aws/record/validations.rb +++ b/lib/aws/record/validations.rb @@ -28,7 +28,7 @@ module Record # # validates_presence_of :title, :author # - # validates_length_of :summary, + # validates_length_of :summary, # :max => 500, # :allow_nil => true, # :allow_blank => true @@ -37,7 +37,7 @@ module Record # # = Conditional Validations # - # Sometimes you only want to validate an attribute under certain + # Sometimes you only want to validate an attribute under certain # conditions. To make this simple, all validation methods accept the # following 3 options: # @@ -55,7 +55,7 @@ module Record # validates_presence_of :created_at, :on => :create # # validates_presence_of :updated_at, :on => :update - # + # # === Validate :if or :unless # # Sometimes you have more complex requirements to determine if/when a @@ -85,7 +85,7 @@ module Record # validates_presence_of :title # # end - # + # module Validations def self.extended base @@ -123,7 +123,7 @@ def self.extended base # # @overload validate(*method_names, options = {}) # @param [Array] method_names A list of methods to call - # during validation. + # during validation. # @param [Hash] options # @option options [Symbol] :on (:save) When this validation is run. # Valid values include: @@ -134,7 +134,7 @@ def self.extended base # to call. The validation will only be run if the return value is # of the method/proc is true (e.g. +:if => :name_changed?+ or # +:if => lambda{|book| book.in_stock? }+). - # @option options [Symbol,String,Proc] :unless Specifies a method or + # @option options [Symbol,String,Proc] :unless Specifies a method or # proc to call. The validation will *not* be run if the return value # is of the method/proc is false. def validate *args @@ -217,7 +217,7 @@ def validate *args # to call. The validation will only be run if the return value is # of the method/proc is true (e.g. +:if => :name_changed?+ or # +:if => lambda{|book| book.in_stock? }+). - # @option options [Symbol,String,Proc] :unless Specifies a method or + # @option options [Symbol,String,Proc] :unless Specifies a method or # proc to call. The validation will *not* be run if the return value # is of the method/proc is false. def validates_acceptance_of *args @@ -238,17 +238,17 @@ def validates_acceptance_of *args # # === Confirmation Value Accessors # - # If your model does not have accessors for the confirmation value + # If your model does not have accessors for the confirmation value # then they will be automatically added. In the example above - # the user class would have an +attr_accessor+ for + # the user class would have an +attr_accessor+ for # +:password_confirmation+. # # === Conditional Validation # # Mostly commonly you only need to validate confirmation of an - # attribute when it has changed. It is therefore suggested to + # attribute when it has changed. It is therefore suggested to # pass an +:if+ condition reflecting this: - # + # # validates_confirmation_of :password, :if => :password_changed? # # === Multi-Valued Attributes @@ -273,7 +273,7 @@ def validates_acceptance_of *args # to call. The validation will only be run if the return value is # of the method/proc is true (e.g. +:if => :name_changed?+ or # +:if => lambda{|book| book.in_stock? }+). - # @option options [Symbol,String,Proc] :unless Specifies a method or + # @option options [Symbol,String,Proc] :unless Specifies a method or # proc to call. The validation will *not* be run if the return value # is of the method/proc is false. def validates_confirmation_of *args @@ -283,9 +283,9 @@ def validates_confirmation_of *args # Validates the number of values for a given attribute. # # === Length vs Count - # + # # +validates_count_of+ validates the number of attribute values, - # whereas +validates_length_of: validates the length of each + # whereas +validates_length_of: validates the length of each # attribute value instead. # # If you need to ensure each attribute value is a given length see @@ -296,7 +296,7 @@ def validates_confirmation_of *args # You can validate there are a certain number of values: # # validates_count_of :parents, :exactly => 2 - # + # # You can also specify a range: # # validates_count_of :tags, :within => (2..10) @@ -318,7 +318,7 @@ def validates_confirmation_of *args # # This validator is intended to for validating attributes that have # an array or set of values. If used on an attribute that - # returns a scalar value (like +nil+ or a string), the count will + # returns a scalar value (like +nil+ or a string), the count will # always be 0 (for +nil+) or 1 (for everything else). # # It is therefore recomended to use +:validates_presence_of+ in @@ -328,10 +328,10 @@ def validates_confirmation_of *args # @overload validates_count_of(*attributes, options = {}, &block) # @param attributes A list of attribute names to validate. # @param [Hash] options - # @option options [Integer] :exactly The exact number of values the - # attribute should have. If this validation option fails the + # @option options [Integer] :exactly The exact number of values the + # attribute should have. If this validation option fails the # error message specified by +:wrong_number+ will be added. - # @option options [Range] :within An range of number of values to + # @option options [Range] :within An range of number of values to # accept. If the attribute has a number of values outside this range # then the +:too_many+ or +:too_few+ error message will be added. # @option options [Integer] :minimum The minimum number of values @@ -359,7 +359,7 @@ def validates_confirmation_of *args # to call. The validation will only be run if the return value is # of the method/proc is true (e.g. +:if => :name_changed?+ or # +:if => lambda{|book| book.in_stock? }+). - # @option options [Symbol,String,Proc] :unless Specifies a method or + # @option options [Symbol,String,Proc] :unless Specifies a method or # proc to call. The validation will *not* be run if the return value # is of the method/proc is false. def validates_count_of *args @@ -374,9 +374,9 @@ def validates_count_of *args # # validates_each(:name) do |record, attribute_name, value| # if value == 'John Doe' - # record.errors.add(attr_name, 'may not be an alias') + # record.errors.add(attr_name, 'may not be an alias') # end - # end + # end # # end # @@ -396,7 +396,7 @@ def validates_count_of *args # to call. The validation will only be run if the return value is # of the method/proc is true (e.g. +:if => :name_changed?+ or # +:if => lambda{|book| book.in_stock? }+). - # @option options [Symbol,String,Proc] :unless Specifies a method or + # @option options [Symbol,String,Proc] :unless Specifies a method or # proc to call. The validation will *not* be run if the return value # is of the method/proc is false. def validates_each *attributes, &block @@ -406,7 +406,7 @@ def validates_each *attributes, &block validators << BlockValidator.new(self, *attributes, &block) end - # Validates that the attribute value is not included in the given + # Validates that the attribute value is not included in the given # enumerable. # # validates_exlusion_of :username, :in => %w(admin administrator) @@ -423,7 +423,7 @@ def validates_each *attributes, &block # validates_exlusion_of :tags, :in => four_letter_words # # end - # + # # @overload validates_exclusion_of(*attributes, options = {}, &block) # @param attributes A list of attribute names to validate. # @param [Hash] options @@ -444,7 +444,7 @@ def validates_each *attributes, &block # to call. The validation will only be run if the return value is # of the method/proc is true (e.g. +:if => :name_changed?+ or # +:if => lambda{|book| book.in_stock? }+). - # @option options [Symbol,String,Proc] :unless Specifies a method or + # @option options [Symbol,String,Proc] :unless Specifies a method or # proc to call. The validation will *not* be run if the return value # is of the method/proc is false. def validates_exclusion_of *args @@ -452,7 +452,7 @@ def validates_exclusion_of *args end # Validates the attribute's value matches the given regular exression. - # + # # validates_format_of :year, :with => /^\d{4}$/ # # You can also perform a not-match using +:without+ instead of +:with+. @@ -471,13 +471,13 @@ def validates_exclusion_of *args # validates_format_of :tags, :with => /^\w{2,10}$/ # # end - # + # # @overload validates_format_of(*attributes, options = {}, &block) # @param attributes A list of attribute names to validate. # @param [Hash] options - # @option options [Regexp] :with If the value matches the given + # @option options [Regexp] :with If the value matches the given # regex, an error will not be added. - # @option options [Regexp] :without If the value matches the given + # @option options [Regexp] :without If the value matches the given # regex, an error will be added. # must match, or an error is added. # @option options [String] :message A custom error message. The default @@ -495,7 +495,7 @@ def validates_exclusion_of *args # to call. The validation will only be run if the return value is # of the method/proc is true (e.g. +:if => :name_changed?+ or # +:if => lambda{|book| book.in_stock? }+). - # @option options [Symbol,String,Proc] :unless Specifies a method or + # @option options [Symbol,String,Proc] :unless Specifies a method or # proc to call. The validation will *not* be run if the return value # is of the method/proc is false. def validates_format_of *args @@ -513,7 +513,7 @@ def validates_format_of *args # # You may use this with multi-valued attributes the same way you use it # with single-valued attributes. - # + # # @overload validates_inclusion_of(*attributes, options = {}, &block) # @param attributes A list of attribute names to validate. # @param [Hash] options @@ -534,7 +534,7 @@ def validates_format_of *args # to call. The validation will only be run if the return value is # of the method/proc is true (e.g. +:if => :name_changed?+ or # +:if => lambda{|book| book.in_stock? }+). - # @option options [Symbol,String,Proc] :unless Specifies a method or + # @option options [Symbol,String,Proc] :unless Specifies a method or # proc to call. The validation will *not* be run if the return value # is of the method/proc is false. def validates_inclusion_of *attributes @@ -546,10 +546,10 @@ def validates_inclusion_of *attributes # validates_lenth_of :username, :within => 3..25 # # === Length vs Count - # + # # +validates_length_of+ validates the length of individual attribute # values, whereas +validates_count_of: validates the number of - # attribute values. + # attribute values. # # If you need to ensure there are certain number of values see # {#validates_count_of} instead. @@ -560,11 +560,11 @@ def validates_inclusion_of *attributes # @option options [Enumerable] :within An enumerable object to # ensure the length of the value falls within. # @option options [Integer] :exactly The exact length a value must be. - # If this validation fails the error message specified by + # If this validation fails the error message specified by # +:wrong_length+ will be added. # @option options [Range] :within An enumerable object which must # include the length of the attribute, or an error will be added. - # If the attribute has a length outside the range then the + # If the attribute has a length outside the range then the # +:too_long+ or +:too_short+ error message will be added. # @option options [Integer] :minimum The minimum length an attribute # value should be. If it is shorter, the +:too_short+ error @@ -597,7 +597,7 @@ def validates_inclusion_of *attributes # to call. The validation will only be run if the return value is # of the method/proc is true (e.g. +:if => :name_changed?+ or # +:if => lambda{|book| book.in_stock? }+). - # @option options [Symbol,String,Proc] :unless Specifies a method or + # @option options [Symbol,String,Proc] :unless Specifies a method or # proc to call. The validation will *not* be run if the return value # is of the method/proc is false. def validates_length_of *args @@ -611,20 +611,20 @@ def validates_length_of *args # === Multi-Valued Attributes # # You can validate multi-valued attributes using this the same way you - # validate single-valued attributes. Each value will be validated + # validate single-valued attributes. Each value will be validated # individually. # # @overload validates_numericality_of(*attributes, options = {}, &block) # @param attributes A list of attribute names to validate. # @param [Hash] options - # @option options [Boolean] :only_integer (false) Adds an error + # @option options [Boolean] :only_integer (false) Adds an error # when valiating and the value is numeric, but it not a whole number. # @option options [Integer] :equal_to When set the value must equal # the given value exactly. May not be used with the greater/less # options. # @option options [Numeric] :greater_than Ensures the attribute # is greater than the given number. - # @option options [Integer] :greater_than_or_equal_to Ensures the + # @option options [Integer] :greater_than_or_equal_to Ensures the # attribute is greater than or equal to the given number. # @option options [Numeric] :less_than Ensures the attribute is less # than the given value. @@ -649,21 +649,21 @@ def validates_length_of *args # to call. The validation will only be run if the return value is # of the method/proc is true (e.g. +:if => :name_changed?+ or # +:if => lambda{|book| book.in_stock? }+). - # @option options [Symbol,String,Proc] :unless Specifies a method or + # @option options [Symbol,String,Proc] :unless Specifies a method or # proc to call. The validation will *not* be run if the return value # is of the method/proc is false. def validates_numericality_of *args validators << NumericalityValidator.new(self, *args) end - # Validates the named attributes are not blank. For validation + # Validates the named attributes are not blank. For validation # purposes, blank values include: # # * +nil+ # * empty string # * anything that responds to #empty? with true # * anything that responds to #blank? with true - # + # # @overload validates_presence_of(*attributes, options = {}, &block) # @param attributes A list of attribute names to validate. # @param [Hash] options @@ -682,7 +682,7 @@ def validates_numericality_of *args # to call. The validation will only be run if the return value is # of the method/proc is true (e.g. +:if => :name_changed?+ or # +:if => lambda{|book| book.in_stock? }+). - # @option options [Symbol,String,Proc] :unless Specifies a method or + # @option options [Symbol,String,Proc] :unless Specifies a method or # proc to call. The validation will *not* be run if the return value # is of the method/proc is false. def validates_presence_of *args diff --git a/lib/aws/record/validator.rb b/lib/aws/record/validator.rb index afcc73a4164..6d7778201e0 100644 --- a/lib/aws/record/validator.rb +++ b/lib/aws/record/validator.rb @@ -30,7 +30,7 @@ def initialize record_class, *attribute_names, &block reject_unknown_options ensure_type([Symbol, Proc], :if, :unless) - ensure_is([:save, :create, :update], :on) + ensure_is([:save, :create, :update], :on) setup(record_class) @@ -44,7 +44,7 @@ def initialize record_class, *attribute_names, &block attr_reader :options def validate record - if + if passes_on_condition?(record) and passes_if_condition?(record) and passes_unless_condition?(record) @@ -84,7 +84,7 @@ def add_accessors klass, *accessors end unless methods.include?(setter) - klass.send(:attr_writer, attr) + klass.send(:attr_writer, attr) klass.send(:public, setter) end diff --git a/lib/aws/record/validators/acceptance.rb b/lib/aws/record/validators/acceptance.rb index 6e1d61abb70..c2df68b385c 100644 --- a/lib/aws/record/validators/acceptance.rb +++ b/lib/aws/record/validators/acceptance.rb @@ -29,7 +29,7 @@ def validate_attribute record, attribute_name, value accepted = case value when '1' then true when true then true - else + else options.has_key?(:accept) ? value == options[:accept] : false diff --git a/lib/aws/record/validators/block.rb b/lib/aws/record/validators/block.rb index 6c08ecc784b..d3aeb895e4c 100644 --- a/lib/aws/record/validators/block.rb +++ b/lib/aws/record/validators/block.rb @@ -13,7 +13,7 @@ module AWS module Record - + # @private class BlockValidator < Validator diff --git a/lib/aws/record/validators/confirmation.rb b/lib/aws/record/validators/confirmation.rb index a9adf15ae8c..b022d54b2b7 100644 --- a/lib/aws/record/validators/confirmation.rb +++ b/lib/aws/record/validators/confirmation.rb @@ -13,7 +13,7 @@ module AWS module Record - + # @private class ConfirmationValidator < Validator diff --git a/lib/aws/record/validators/count.rb b/lib/aws/record/validators/count.rb index 431026e53a9..b401473c53d 100644 --- a/lib/aws/record/validators/count.rb +++ b/lib/aws/record/validators/count.rb @@ -72,7 +72,7 @@ def validate_attribute record, attribute_name, value # @private protected def wrong_number exactly, got - msg = options[:wrong_number] || + msg = options[:wrong_number] || "has the wrong number of values (should have %{exactly})" interpolate(msg, :exactly => exactly, :count => got) end @@ -93,7 +93,7 @@ def too_many max, got protected def interpolate message_with_placeholders, values - msg = message_with_placeholders.dup + msg = message_with_placeholders.dup values.each_pair do |key,value| msg.gsub!(/%\{#{key}\}/, value.to_s) end diff --git a/lib/aws/record/validators/exclusion.rb b/lib/aws/record/validators/exclusion.rb index 35bad868824..ffa1886eed7 100644 --- a/lib/aws/record/validators/exclusion.rb +++ b/lib/aws/record/validators/exclusion.rb @@ -27,7 +27,7 @@ def setup record_class def validate_attribute record, attribute_name, value_or_values each_value(value_or_values) do |value| included = value_included?(value) - record.errors.add(attribute_name, message) if included + record.errors.add(attribute_name, message) if included end end diff --git a/lib/aws/record/validators/format.rb b/lib/aws/record/validators/format.rb index 8bd68b3dcaa..e045cad8e7d 100644 --- a/lib/aws/record/validators/format.rb +++ b/lib/aws/record/validators/format.rb @@ -13,12 +13,12 @@ module AWS module Record - + # @private class FormatValidator < Validator ACCEPTED_OPTIONS = [ - :with, :without, + :with, :without, :message, :allow_nil, :allow_blank, :on, :if, :unless, ] diff --git a/lib/aws/record/validators/length.rb b/lib/aws/record/validators/length.rb index 1dacb50b29e..52aa1b1af1d 100644 --- a/lib/aws/record/validators/length.rb +++ b/lib/aws/record/validators/length.rb @@ -13,7 +13,7 @@ module AWS module Record - + # @private class LengthValidator < Validator @@ -69,7 +69,7 @@ def validate_attribute record, attribute_name, value_or_values # @private protected def wrong_length exactly, got - msg = options[:wrong_length] || + msg = options[:wrong_length] || "is the wrong length (should be %{exactly} characters)" interpolate(msg, :exactly => exactly, :length => got) end @@ -77,7 +77,7 @@ def wrong_length exactly, got # @private protected def too_short min, got - msg = options[:too_short] || + msg = options[:too_short] || "is too short (minimum is %{minimum} characters)" interpolate(msg, :minimum => min, :length => got) end @@ -85,14 +85,14 @@ def too_short min, got # @private protected def too_long max, got - msg = options[:too_long] || + msg = options[:too_long] || "is too long (maximum is %{maximum} characters)" interpolate(msg, :maximum => max, :length => got) end protected def interpolate message_with_placeholders, values - msg = message_with_placeholders.dup + msg = message_with_placeholders.dup values.each_pair do |key,value| msg.gsub!(/%\{#{key}\}/, value.to_s) end diff --git a/lib/aws/record/validators/method.rb b/lib/aws/record/validators/method.rb index ccb32e0c099..0dc51d51d0f 100644 --- a/lib/aws/record/validators/method.rb +++ b/lib/aws/record/validators/method.rb @@ -13,7 +13,7 @@ module AWS module Record - + # Uses the base validator class to call user-defined validation methods. # @private class MethodValidator < Validator diff --git a/lib/aws/record/validators/numericality.rb b/lib/aws/record/validators/numericality.rb index 7be7f2d9065..f9e24091bea 100644 --- a/lib/aws/record/validators/numericality.rb +++ b/lib/aws/record/validators/numericality.rb @@ -13,7 +13,7 @@ module AWS module Record - + # @private class NumericalityValidator < Validator @@ -25,10 +25,10 @@ class NumericalityValidator < Validator ] COMPARISONS = { - :equal_to => :==, + :equal_to => :==, :greater_than => :>, :greater_than_or_equal_to => :>=, - :less_than => :<, + :less_than => :<, :less_than_or_equal_to => :<=, :even => lambda{|value| value.to_i % 2 == 0 }, :odd => lambda{|value| value.to_i % 2 == 1 }, @@ -38,7 +38,7 @@ def setup record_class ensure_exclusive(:odd, :even) - ensure_exclusive(:equal_to, + ensure_exclusive(:equal_to, [:greater_than, :greater_than_or_equal_to, :less_than, :less_than_or_equal_to]) @@ -46,7 +46,7 @@ def setup record_class ensure_type(TrueClass, :odd, :even) - ensure_type([Numeric, Symbol, Proc], + ensure_type([Numeric, Symbol, Proc], :greater_than, :greater_than_or_equal_to, :less_than, :less_than_or_equal_to, :equal_to) @@ -76,7 +76,7 @@ def validate_attribute record, attribute_name, raw record.errors.add(attribute_name, message_for(error_type)) return end - + COMPARISONS.each do |option,method| next unless options.has_key?(option) diff --git a/lib/aws/record/validators/presence.rb b/lib/aws/record/validators/presence.rb index 58b7c5952c7..ec4254ebd97 100644 --- a/lib/aws/record/validators/presence.rb +++ b/lib/aws/record/validators/presence.rb @@ -18,7 +18,7 @@ module Record class PresenceValidator < Validator ACCEPTED_OPTIONS = [:message, :allow_nil, :allow_blank, :on, :if, :unless] - + def validate_attribute record, attribute_name, value blank = case diff --git a/lib/aws/route_53/hosted_zone_collection.rb b/lib/aws/route_53/hosted_zone_collection.rb index 5f0ef403bc3..bbba518f547 100644 --- a/lib/aws/route_53/hosted_zone_collection.rb +++ b/lib/aws/route_53/hosted_zone_collection.rb @@ -83,7 +83,7 @@ def _each_item next_token, limit, options = {}, &block resp.data[:hosted_zones].each do |details| hosted_zone = HostedZone.new_from( :list_hosted_zones, - details, + details, details[:id], :config => config) diff --git a/lib/aws/s3.rb b/lib/aws/s3.rb index ac80780d96f..48ed0fc068c 100644 --- a/lib/aws/s3.rb +++ b/lib/aws/s3.rb @@ -18,7 +18,7 @@ module AWS # Provides an expressive, object-oriented interface to Amazon S3. # - # To use Amazon S3 you must first + # To use Amazon S3 you must first # {sign up here}[http://aws.amazon.com/s3/]. # # For more information about Amazon S3, see: @@ -28,7 +28,7 @@ module AWS # # = Credentials # - # You can setup default credentials for all AWS services via + # You can setup default credentials for all AWS services via # AWS.config: # # AWS.config( @@ -58,7 +58,7 @@ module AWS # puts bucket.name # end # - # See {BucketCollection} and {Bucket} for more information on working + # See {BucketCollection} and {Bucket} for more information on working # with buckets. # # = Objects @@ -74,7 +74,7 @@ module AWS # # == Reading and Writing an Object # - # The example above returns an {S3Object}. You call {S3Object#write} and + # The example above returns an {S3Object}. You call {S3Object#write} and # {S3Object#read} to upload to and download from S3 respectively. # # # streaming upload a file to S3 @@ -91,7 +91,7 @@ module AWS # # You can enumerate objects in your buckets. # - # # enumerate ALL objects in the bucket (even if the bucket contains + # # enumerate ALL objects in the bucket (even if the bucket contains # # more than 1k objects) # bucket.objects.each do |obj| # puts obj.key @@ -143,7 +143,7 @@ class S3 include Core::ServiceInterface - # @return [BucketCollection] Returns a collection that represents all + # @return [BucketCollection] Returns a collection that represents all # buckets in the account. def buckets BucketCollection.new(:config => @config) diff --git a/lib/aws/s3/access_control_list.rb b/lib/aws/s3/access_control_list.rb index 21f8654231c..3ea99469bd3 100644 --- a/lib/aws/s3/access_control_list.rb +++ b/lib/aws/s3/access_control_list.rb @@ -137,7 +137,7 @@ def signal_attribute # @private def type_for_attr(attr) - { + { :amazon_customer_email => "AmazonCustomerByEmail", :canonical_user_id => "CanonicalUser", :group_uri => "Group", diff --git a/lib/aws/s3/acl_options.rb b/lib/aws/s3/acl_options.rb index 00b1cbcf212..7af21adcac2 100644 --- a/lib/aws/s3/acl_options.rb +++ b/lib/aws/s3/acl_options.rb @@ -23,7 +23,7 @@ module ACLOptions protected - # @param [Symbol,String,Hash,AccessControlList] acl Accepts an ACL + # @param [Symbol,String,Hash,AccessControlList] acl Accepts an ACL # description in one of the following formats: # # ==== Canned ACL @@ -40,7 +40,7 @@ module ACLOptions # * +:log_delivery_write+ (bucket-only) # # Here is an example of providing a canned ACL to a bucket: - # + # # s3.buckets['bucket-name'].acl = :public_read # # ==== ACL Grant Hash @@ -54,7 +54,7 @@ module ACLOptions # * +:grant_write_acp+ # * +:grant_full_control+ # - # Grantee strings (values) should be formatted like some of the + # Grantee strings (values) should be formatted like some of the # following examples: # # id="8a6925ce4adf588a4532142d3f74dd8c71fa124b1ddee97f21c32aa379004fef" @@ -74,7 +74,7 @@ module ACLOptions # # ==== AcessControlList Object # - # You can build an ACL using the {AccessControlList} class and + # You can build an ACL using the {AccessControlList} class and # pass this object. # # acl = AWS::S3::AccessControlList.new @@ -100,7 +100,7 @@ module ACLOptions # FULL_CONTROL # # - # + # # XML # # @return [Hash] Returns a hash of options suitable for diff --git a/lib/aws/s3/bucket_collection.rb b/lib/aws/s3/bucket_collection.rb index 97e13271f00..604876eb85f 100644 --- a/lib/aws/s3/bucket_collection.rb +++ b/lib/aws/s3/bucket_collection.rb @@ -14,7 +14,7 @@ module AWS class S3 - # Represents a collection of buckets. + # Represents a collection of buckets. # # You can use this to create a bucket: # @@ -72,7 +72,7 @@ class BucketCollection # bucket = s3.buckets.create("my-us-west-bucket") # bucket.location_constraint # => "us-west-1" # - # @option options [Symbol,String] :acl (:private) Sets the ACL of the + # @option options [Symbol,String] :acl (:private) Sets the ACL of the # bucket you are creating. Valid Values include: # * +:private+ # * +:public_read+ @@ -95,10 +95,10 @@ def create bucket_name, options = {} options[:acl] = acl.to_s.tr('_', '-') end - # auto set the location constraint for the user if it is not - # passed in and the endpoint is not the us-standard region. don't - # override the location constraint though, even it is wrong, - unless + # auto set the location constraint for the user if it is not + # passed in and the endpoint is not the us-standard region. don't + # override the location constraint though, even it is wrong, + unless config.s3_endpoint == 's3.amazonaws.com' or options[:location_constraint] then diff --git a/lib/aws/s3/client/xml.rb b/lib/aws/s3/client/xml.rb index 815a2797f7b..86ef3beca08 100644 --- a/lib/aws/s3/client/xml.rb +++ b/lib/aws/s3/client/xml.rb @@ -141,7 +141,7 @@ module XML element("DeleteMarker") { boolean_value } list end - element("Error") { list; rename(:errors) } + element("Error") { list; rename(:errors) } end CompleteMultipartUpload = BaseGrammar.customize diff --git a/lib/aws/s3/object_metadata.rb b/lib/aws/s3/object_metadata.rb index ab7f7979017..a5dbac8eb7a 100644 --- a/lib/aws/s3/object_metadata.rb +++ b/lib/aws/s3/object_metadata.rb @@ -80,7 +80,7 @@ def method_missing name, *args, &blk self[name] end - # @return [Hash] Returns the user-generated metadata stored with + # @return [Hash] Returns the user-generated metadata stored with # this S3 Object. def to_h options = {} diff --git a/lib/aws/s3/object_version.rb b/lib/aws/s3/object_version.rb index ae5fabacbbb..c59776ca73b 100644 --- a/lib/aws/s3/object_version.rb +++ b/lib/aws/s3/object_version.rb @@ -16,8 +16,8 @@ class S3 # Represents a single version of an S3Object. # - # When you enable versioning on a S3 bucket, writing to an object - # will create an object version instead of replacing the existing + # When you enable versioning on a S3 bucket, writing to an object + # will create an object version instead of replacing the existing # object. class ObjectVersion @@ -112,14 +112,14 @@ def latest? end # If you delete an object in a versioned bucket, a delete marker - # is created. + # is created. # @return [Boolean] Returns true if this version is a delete marker. def delete_marker? if @delete_marker.nil? begin # S3 responds with a 405 (method not allowed) when you try # to HEAD an s3 object version that is a delete marker - metadata['foo'] + metadata['foo'] @delete_marker = false rescue Errors::MethodNotAllowed => error @delete_marker = true diff --git a/lib/aws/s3/policy.rb b/lib/aws/s3/policy.rb index dd81c59c248..0b4279f0ddd 100644 --- a/lib/aws/s3/policy.rb +++ b/lib/aws/s3/policy.rb @@ -58,11 +58,11 @@ def resource_arn resource "#{prefix}#{resource.name}" when S3Object "#{prefix}#{resource.bucket.name}/#{resource.key}" - when ObjectCollection + when ObjectCollection "#{prefix}#{resource.bucket.name}/#{resource.prefix}*" when /^arn:/ resource - else + else "arn:aws:s3:::#{resource}" end end diff --git a/lib/aws/s3/prefixed_collection.rb b/lib/aws/s3/prefixed_collection.rb index 355f1205e1e..8adb8ffa290 100644 --- a/lib/aws/s3/prefixed_collection.rb +++ b/lib/aws/s3/prefixed_collection.rb @@ -25,7 +25,7 @@ def initialize *args args.push(options) super(*args) end - + # @return [String,nil] The prefix of this collection. attr_reader :prefix diff --git a/lib/aws/s3/presigned_post.rb b/lib/aws/s3/presigned_post.rb index c99413c2734..e5537a6322e 100644 --- a/lib/aws/s3/presigned_post.rb +++ b/lib/aws/s3/presigned_post.rb @@ -494,14 +494,14 @@ def generate_conditions conditions = self.conditions.inject([]) do |list, (field, field_conds)| list + field_conds - end - + end + conditions << { "bucket" => bucket.name } conditions += key_conditions conditions += optional_fields.map { |(n, v)| Hash[[[n, v]]] } conditions += range_conditions conditions += ignored_conditions - + if token = config.credential_provider.session_token conditions << { "x-amz-security-token" => token } end diff --git a/lib/aws/s3/s3_object.rb b/lib/aws/s3/s3_object.rb index 105f5ea7b3c..ee6c3eb1e9a 100644 --- a/lib/aws/s3/s3_object.rb +++ b/lib/aws/s3/s3_object.rb @@ -221,7 +221,7 @@ class S3 # encrypt and decrypt for you. You can do this globally or for a # single S3 interface # - # # all objects uploaded/downloaded with this s3 object will be + # # all objects uploaded/downloaded with this s3 object will be # # encrypted/decrypted # s3 = AWS::S3.new(:s3_encryption_key => "MY_KEY") # @@ -1379,7 +1379,7 @@ def http_method(input) def request_for_signing(options) - port = [443, 80].include?(config.s3_port) ? + port = [443, 80].include?(config.s3_port) ? (options[:secure] ? 443 : 80) : config.s3_port diff --git a/lib/aws/s3/tree/child_collection.rb b/lib/aws/s3/tree/child_collection.rb index e88682cb6fa..72dbd8d5e0c 100644 --- a/lib/aws/s3/tree/child_collection.rb +++ b/lib/aws/s3/tree/child_collection.rb @@ -23,9 +23,9 @@ class ChildCollection # @private def initialize parent, collection, options = {} - options = { + options = { :prefix => nil, - :delimiter => '/', + :delimiter => '/', :append => true, }.merge(options) @@ -58,7 +58,7 @@ def initialize parent, collection, options = {} attr_reader :delimiter # @return [Boolean] Returns true if the tree is set to auto-append - # the delimiter to the prefix when the prefix does not end with + # the delimiter to the prefix when the prefix does not end with # the delimiter. def append? @append diff --git a/lib/aws/s3/tree/leaf_node.rb b/lib/aws/s3/tree/leaf_node.rb index 590666f59f2..deea854f3b2 100644 --- a/lib/aws/s3/tree/leaf_node.rb +++ b/lib/aws/s3/tree/leaf_node.rb @@ -64,7 +64,7 @@ def object end end - # @return [ObjectVersion] Returns the object version this leaf + # @return [ObjectVersion] Returns the object version this leaf # node represents. def version if @member.kind_of?(ObjectVersion) @@ -74,7 +74,7 @@ def version end end - # @return [MultipartUpload] Returns the object version this leaf + # @return [MultipartUpload] Returns the object version this leaf # node represents. def upload if @member.kind_of?(MultipartUpload) diff --git a/lib/aws/s3/website_configuration.rb b/lib/aws/s3/website_configuration.rb index f9f7b9aeb37..3e1c926b346 100644 --- a/lib/aws/s3/website_configuration.rb +++ b/lib/aws/s3/website_configuration.rb @@ -22,7 +22,7 @@ class WebsiteConfiguration # Describes the redirect behavior for every request to this # bucket's website endpoint. If this element is present, no # other options are are allowed. - # * +:host_name+ - (*required*, String) + # * +:host_name+ - (*required*, String) # Name of the host where requests will be redirected. # * +:protocol+ - (String) # Protocol to use (http, https) when redirecting requests. The diff --git a/lib/aws/simple_db.rb b/lib/aws/simple_db.rb index 6a7fa274ac7..99920a7c1ae 100644 --- a/lib/aws/simple_db.rb +++ b/lib/aws/simple_db.rb @@ -18,7 +18,7 @@ module AWS # This class is the starting point for working with Amazon SimpleDB. # - # To use Amazon SimpleDB you must first + # To use Amazon SimpleDB you must first # {sign up here}[http://aws.amazon.com/simpledb/]. # # For more information about Amazon SimpleDB: @@ -28,13 +28,13 @@ module AWS # # = Credentials # - # You can setup default credentials for all AWS services via + # You can setup default credentials for all AWS services via # AWS.config: # # AWS.config( # :access_key_id => 'YOUR_ACCESS_KEY_ID', # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') - # + # # Or you can set them directly on the SimpleDB interface: # # sdb = AWS::SimpleDB.new( @@ -49,12 +49,12 @@ module AWS # # These are modeled with the following classes: # - # * {DomainCollection} - # * {Domain} - # * {ItemCollection} - # * {Item} - # * {AttributeCollection} - # * {Attribute} + # * {DomainCollection} + # * {Domain} + # * {ItemCollection} + # * {Item} + # * {AttributeCollection} + # * {Attribute} # # The collection classes listed above make it easy to enumerate, # the objects they represent. They also make it easy to perform @@ -73,7 +73,7 @@ module AWS # # = Items & Attributes # - # Items exist in SimpleDB when they have attributes. You can delete an + # Items exist in SimpleDB when they have attributes. You can delete an # item by removing all of its attributes. You create an item by adding # an attribute to it. # @@ -148,7 +148,7 @@ class SimpleDB autoload :Domain, 'aws/simple_db/domain' autoload :DomainCollection, 'aws/simple_db/domain_collection' autoload :DomainMetadata, 'aws/simple_db/domain_metadata' - autoload :Errors, 'aws/simple_db/errors' + autoload :Errors, 'aws/simple_db/errors' autoload :ExpectConditionOption, 'aws/simple_db/expect_condition_option' autoload :Item, 'aws/simple_db/item' autoload :ItemCollection, 'aws/simple_db/item_collection' @@ -161,7 +161,7 @@ class SimpleDB # Returns a collection object that represents the domains in your # account. # - # @return [DomainCollection] Returns a collection representing all your + # @return [DomainCollection] Returns a collection representing all your # domains. def domains DomainCollection.new(:config => config) @@ -177,15 +177,15 @@ def domains # === Other Modes # # You can also use this same function to disable consistent reads inside - # a block. This is useful if you have consistent reads enabled by + # a block. This is useful if you have consistent reads enabled by # default: # # AWS::SimpleDB.consistent_reads(false) do # # ... # end - # + # # @param [Boolean] state (true) When true, all SimpleDB read operations - # will be consistent reads inside the block. When false, all + # will be consistent reads inside the block. When false, all # reads operations will not be consistent reads. The previous state # will be restored after the block executes. # @return Returns the final block value. diff --git a/lib/aws/simple_db/attribute.rb b/lib/aws/simple_db/attribute.rb index 33d450fdc7d..fe2dc3b9a6f 100644 --- a/lib/aws/simple_db/attribute.rb +++ b/lib/aws/simple_db/attribute.rb @@ -51,7 +51,7 @@ def set *values nil end - # Appends values to this attribute. Duplicate values are ignored + # Appends values to this attribute. Duplicate values are ignored # by SimpleDB. # # @example Adding a list of values @@ -81,7 +81,7 @@ def add *values # item.attributes['color'].delete('red', 'blue') # # @param values One ore more values to remove from this attribute. - # If values is empty, then all attribute values are deleted + # If values is empty, then all attribute values are deleted # (which deletes this attribute). # @return [nil] def delete *values @@ -96,14 +96,14 @@ def delete *values nil end - # Yields once for each value on this attribute. + # Yields once for each value on this attribute. # # @yield [attribute_value] Yields once for each domain in the account. # @yieldparam [String] attribute_value - # @param [Hash] options + # @param [Hash] options # @option options [Boolean] :consistent_read (false) A consistent read # returns values that reflects all writes that received a successful - # response prior to the read. + # response prior to the read. # @return [nil] def each options = {}, &block @@ -127,10 +127,10 @@ def each options = {}, &block # item.attributes['ratings'].values # #=> ['5', '3', '4'] # - # @param [Hash] options + # @param [Hash] options # @option options [Boolean] :consistent_read (false) A consistent read # returns values that reflects all writes that received a successful - # response prior to the read. + # response prior to the read. # @return [Array] An array of attribute values def values options = {} values = [] diff --git a/lib/aws/simple_db/attribute_collection.rb b/lib/aws/simple_db/attribute_collection.rb index 3b0109b058f..3c7e25dcaaf 100644 --- a/lib/aws/simple_db/attribute_collection.rb +++ b/lib/aws/simple_db/attribute_collection.rb @@ -37,7 +37,7 @@ def initialize item, options = {} # # You can ask for any attribute by name. The attribute may or may not # actually exist in SimpleDB. - # + # # @example Get an attribute by symbol or string name # colors = item.attributes[:colors] # colors = item.attributes['colors'] @@ -64,12 +64,12 @@ def []= attribute_name, *values # puts "#{name}: #{value}" # end # - # @yield [attribute_name, attribute_value] Yields once for every + # @yield [attribute_name, attribute_value] Yields once for every # attribute value on the item. # @yieldparam [String] attribute_name # @yieldparam [String] attribute_value - # @param [Hash] options - # @option options [Boolean] :consistent_read (false) Causes this + # @param [Hash] options + # @option options [Boolean] :consistent_read (false) Causes this # method to yield the most current attributes for this item. # @return [nil] def each_value options = {}, &block @@ -101,13 +101,13 @@ def each_value options = {}, &block # on the item. Yields each attribute only one time, even it # has multiple values. # @yieldparam [Attribute] attribute - # @param [Hash] options - # @option options [Boolean] :consistent_read (false) Causes this + # @param [Hash] options + # @option options [Boolean] :consistent_read (false) Causes this # method to yield the most current attributes for this item. # @return [nil] def each options = {}, &block yielded = {} - each_value(options) do |attribute_name, attribute_value| + each_value(options) do |attribute_name, attribute_value| unless yielded[attribute_name] attribute = self[attribute_name] yield(attribute) @@ -119,7 +119,7 @@ def each options = {}, &block # Replaces attributes for the {#item}. # - # The +attributes_hash+ should have attribute names as keys. The + # The +attributes_hash+ should have attribute names as keys. The # hash values should be either strings or arrays of strings. # # Attributes not named in this hash are left alone. Attributes named @@ -128,7 +128,7 @@ def each options = {}, &block # @example # # item.attributes.set( - # 'colors' => ['red', 'blue'], + # 'colors' => ['red', 'blue'], # 'category' => 'clearance') # # @param [Hash] attributes @@ -140,13 +140,13 @@ def replace attributes # Adds values to attributes on the {#item}. # - # The +attributes_hash+ should have attribute names as keys. The + # The +attributes_hash+ should have attribute names as keys. The # hash values should be either strings or arrays of strings. # # @example # # item.attributes.add( - # 'colors' => ['red', 'blue'], + # 'colors' => ['red', 'blue'], # 'category' => 'clearance') # # @param[Hash] attribute_hash @@ -186,8 +186,8 @@ def put options = {} # item.attributes.to_h # #=> { 'colors' => ['red','blue'], 'size' => ['large'] } # - # @param [Hash] options - # @option options [Boolean] :consistent_read (false) Causes this + # @param [Hash] options + # @option options [Boolean] :consistent_read (false) Causes this # method to return the most current attributes values. # @return [Hash] def to_h options = {} diff --git a/lib/aws/simple_db/consistent_read_option.rb b/lib/aws/simple_db/consistent_read_option.rb index 8200545ddf9..e85c4d7b4a2 100644 --- a/lib/aws/simple_db/consistent_read_option.rb +++ b/lib/aws/simple_db/consistent_read_option.rb @@ -13,7 +13,7 @@ module AWS class SimpleDB - + # @private module ConsistentReadOption @@ -24,19 +24,19 @@ module ConsistentReadOption # * +:consistent_read+ option # * SimpleDB.consistent_reads block value # * AWS.config.simple_db_consistent_reads? - # + # # @return [Boolean] Returns true if a read should be made consistently # to SimpleDB. def consistent_read options if options.has_key?(:consistent_read) - options[:consistent_read] ? true : false + options[:consistent_read] ? true : false elsif SimpleDB.send(:in_consistent_reads_block?) SimpleDB.send(:consistent_reads_state) else config.simple_db_consistent_reads? end end - + end end end diff --git a/lib/aws/simple_db/domain.rb b/lib/aws/simple_db/domain.rb index 2bbd3d929a0..312025d165e 100644 --- a/lib/aws/simple_db/domain.rb +++ b/lib/aws/simple_db/domain.rb @@ -102,9 +102,9 @@ def items # @return [Boolean] Returns true if the domains are the same. def == other - other.is_a?(Domain) and + other.is_a?(Domain) and other.name == name and - other.config.simple_db_endpoint == config.simple_db_endpoint + other.config.simple_db_endpoint == config.simple_db_endpoint end alias_method :eql?, :== diff --git a/lib/aws/simple_db/domain_collection.rb b/lib/aws/simple_db/domain_collection.rb index 272c3e06ab7..039db890bc7 100644 --- a/lib/aws/simple_db/domain_collection.rb +++ b/lib/aws/simple_db/domain_collection.rb @@ -43,11 +43,11 @@ class DomainCollection # Creates a domain in SimpleDB and returns a domain object. # # @note This operation might take 10 or more seconds to complete. - # @note Creating a domain in SimpleDB is an idempotent operation; - # running it multiple times using the same domain name will not + # @note Creating a domain in SimpleDB is an idempotent operation; + # running it multiple times using the same domain name will not # result in an error. # @note You can create up to 250 domains per account. - # @param [String] domain_name + # @param [String] domain_name # @return [Domain] Returns a new domain with the given name. def create(domain_name) client.create_domain(:domain_name => domain_name) @@ -56,7 +56,7 @@ def create(domain_name) # Returns a domain object with the given name. # - # @note This does not attempt to create the domain if it does not + # @note This does not attempt to create the domain if it does not # already exist in SimpleDB. Use {#create} to add a domain to SDB. # # @param [String] domain_name The name of the domain to return. diff --git a/lib/aws/simple_db/domain_metadata.rb b/lib/aws/simple_db/domain_metadata.rb index 77c2b43edb6..ad390645fe1 100644 --- a/lib/aws/simple_db/domain_metadata.rb +++ b/lib/aws/simple_db/domain_metadata.rb @@ -32,7 +32,7 @@ class SimpleDB # :item_names_size_bytes => 3, # :attribute_name_count => 3 # } - # + # class DomainMetadata include Core::Model @@ -69,19 +69,19 @@ def item_names_size_bytes self.to_h[:item_names_size_bytes] end - # @return [Integer] The number of unique attribute names in the + # @return [Integer] The number of unique attribute names in the # {#domain}. def attribute_name_count self.to_h[:attribute_name_count] end - # @return [Integer] The total size of all unique attribute names, + # @return [Integer] The total size of all unique attribute names, # in bytes. def attribute_names_size_bytes self.to_h[:attribute_names_size_bytes] end - # @return [Integer] The number of all attribute name/value pairs in + # @return [Integer] The number of all attribute name/value pairs in # the {#domain}. def attribute_value_count self.to_h[:attribute_value_count] @@ -98,7 +98,7 @@ def timestamp self.to_h[:timestamp] end - # @return [Hash] A hash of all of the metadata attributes for + # @return [Hash] A hash of all of the metadata attributes for # this {#domain}. def to_h meta = client.domain_metadata(:domain_name => domain.name) diff --git a/lib/aws/simple_db/item.rb b/lib/aws/simple_db/item.rb index e8490b3ac08..3b4fd26a763 100644 --- a/lib/aws/simple_db/item.rb +++ b/lib/aws/simple_db/item.rb @@ -35,7 +35,7 @@ def initialize domain, name, options = {} @name = name super end - + # @return [Domain] The domain this item belongs to. attr_reader :domain @@ -50,9 +50,9 @@ def attributes # Deletes the item and all of its attributes from SimpleDB. # @param [Hash] options - # @option options [Hash] :if Pass a hash with a single key (attribute + # @option options [Hash] :if Pass a hash with a single key (attribute # name) and a single value (the attribute value). This causes the - # delete to become conditional. + # delete to become conditional. # @option options [String,Symbol] :unless Pass an attribute name. This # causes the delete to become conditional on that attribute not # existing. @@ -80,7 +80,7 @@ def data options = {} end def == other - other.is_a?(Item) and + other.is_a?(Item) and other.domain == domain and other.name == name end diff --git a/lib/aws/simple_db/item_collection.rb b/lib/aws/simple_db/item_collection.rb index 3ecb41cf512..85cca854fa4 100644 --- a/lib/aws/simple_db/item_collection.rb +++ b/lib/aws/simple_db/item_collection.rb @@ -56,16 +56,16 @@ def initialize domain, options = {} super end - # Creates a new item in SimpleDB with the given attributes: + # Creates a new item in SimpleDB with the given attributes: # # domain.items.create('shirt', { - # 'colors' => ['red', 'blue'], + # 'colors' => ['red', 'blue'], # 'category' => 'clearance'}) # # @overload create(item_name, attributes) # @param [String] item_name The name of the item as you want it stored # in SimpleDB. - # @param [Hash] attributes A hash of attribute names and values + # @param [Hash] attributes A hash of attribute names and values # you want to store in SimpleDB. # @return [Item] Returns a reference to the object that was created. def create item_name, *args @@ -99,7 +99,7 @@ def [] item_name # * AWS::SimpleDB::ItemData objects (some or all attributes populated) # # The default mode of an ItemCollection is to yield Item objects with - # no populated attributes. + # no populated attributes. # # # only receives item names from SimpleDB # domain.items.each do |item| @@ -151,14 +151,14 @@ def [] item_name # will be yielded (see {#order}). # # @option options :limit [Integer] The maximum number of - # items to fetch from SimpleDB. + # items to fetch from SimpleDB. # # @option options :batch_size Specifies a maximum number of records # to fetch from SimpleDB in a single request. SimpleDB may return # fewer items than :batch_size per request, but never more. # Generally you should not need to specify this option. # - # @return [String,nil] Returns a next token that can be used with + # @return [String,nil] Returns a next token that can be used with # the exact same SimpleDB select expression to get more results. # A next token is returned ONLY if there was a limit on the # expression, otherwise all items will be enumerated and @@ -217,7 +217,7 @@ def count options = {}, &block response = select_request(options, next_token) - if + if domain_item = response.items.first and count_attribute = domain_item.attributes.first then @@ -238,13 +238,13 @@ def count options = {}, &block # # the selection mode (item names only or with attributes). # # # def page options = {} -# +# # handle_query_options(options) do |collection, opts| # return collection.page(opts) # end -# +# # super(options) -# +# # end # Specifies a list of attributes select from SimpleDB. @@ -260,14 +260,14 @@ def count options = {}, &block # domain.items.select(:all).each {|item_data| ... } # # Calling #select causes #each to yield {ItemData} objects - # with #attribute hashes, instead of {Item} objects with + # with #attribute hashes, instead of {Item} objects with # an item name. # # @param [Symbol, String, or Array] attributes The attributes to # retrieve. This can be: - # + # # * +:all+ or '*' to request all attributes for each item - # + # # * A list or array of attribute names as strings or symbols # # Attribute names may contain any characters that are valid @@ -451,14 +451,14 @@ def limit *args # Applies standard scope options (e.g. :where => 'foo') and removes them from # the options hash by calling their method (e.g. by calling #where('foo')). - # Yields only if there were scope options to apply. + # Yields only if there were scope options to apply. # @private protected def handle_query_options(*args) options = args.last.is_a?(Hash) ? args.pop : {} - if + if query_options = options.keys & [:select, :where, :order, :limit] and !query_options.empty? then @@ -486,7 +486,7 @@ def _each_item next_token, max, options = {}, &block response = select_request(options, next_token, max) - if output_list == 'itemName()' + if output_list == 'itemName()' response.items.each do |item| yield(self[item.name]) end @@ -497,7 +497,7 @@ def _each_item next_token, max, options = {}, &block end response[:next_token] - + end protected @@ -559,7 +559,7 @@ def limit_clause # @private protected def collection_with options - ItemCollection.new(domain, { + ItemCollection.new(domain, { :output_list => output_list, :conditions => conditions, :sort_instructions => sort_instructions, diff --git a/lib/aws/simple_db/put_attributes.rb b/lib/aws/simple_db/put_attributes.rb index f842d9dd4ce..455b2b162b3 100644 --- a/lib/aws/simple_db/put_attributes.rb +++ b/lib/aws/simple_db/put_attributes.rb @@ -27,9 +27,9 @@ def attribute_hashes attributes, replace attribute_hashes = [] attributes.each_pair do |attribute_name,values| [values].flatten.each do |value| - attribute_hashes << { - :name => attribute_name.to_s, - :value => value.to_s, + attribute_hashes << { + :name => attribute_name.to_s, + :value => value.to_s, :replace => replace, } unless [:if, :unless].include?(attribute_name) end diff --git a/lib/aws/simple_email_service.rb b/lib/aws/simple_email_service.rb index 51b52bd0189..92f209dfb6f 100644 --- a/lib/aws/simple_email_service.rb +++ b/lib/aws/simple_email_service.rb @@ -15,11 +15,11 @@ require 'aws/simple_email_service/config' module AWS - - # This class is the starting point for working with Amazon + + # This class is the starting point for working with Amazon # SimpleEmailService (SES). # - # To use Amazon SimpleEmailService you must first + # To use Amazon SimpleEmailService you must first # {sign up here}[http://aws.amazon.com/ses/] # # For more information about Amazon SimpleEmailService: @@ -29,13 +29,13 @@ module AWS # # = Credentials # - # You can setup default credentials for all AWS services via + # You can setup default credentials for all AWS services via # AWS.config: # # AWS.config( # :access_key_id => 'YOUR_ACCESS_KEY_ID', # :secret_access_key => 'YOUR_SECRET_ACCESS_KEY') - # + # # Or you can set them directly on the SimpleEmailService interface: # # ses = AWS::SimpleEmailService.new( @@ -60,7 +60,7 @@ module AWS # Before you can send emails, you need to verify one or more identities. # Identities are email addresses or domain names that you have control over. # Until you have {requested production access}[http://docs.amazonwebservices.com/ses/latest/DeveloperGuide/InitialSetup.Customer.html] - # you will only be able to send emails to and from verified email addresses + # you will only be able to send emails to and from verified email addresses # and domains. # # == Verifying Email Addresses @@ -77,7 +77,7 @@ module AWS # == Verifying Domains # # You can also verify an entire domain for sending and receiving emails. - # + # # identity = ses.identities.verify('yourdomain.com') # identity.verification_token # #=> "216D+lZbhUL0zOoAkC83/0TAl5lJSzLmzsOjtXM7AeM=" @@ -87,7 +87,7 @@ module AWS # more details. # # == Listing Identities - # + # # You can enumerate all identities: # # ses.identities.map(&:identity) @@ -118,7 +118,7 @@ module AWS # :from => 'sender@domain.com', # :to => 'receipient@domain.com', # :body_text => 'Sample email text.', - # :body_html => '

        Sample Email

        ') + # :body_html => '

        Sample Email

        ') # # If you need to send email with attachments or have other special needs # that send_email does not support you can use {#send_raw_email}. @@ -147,7 +147,7 @@ module AWS # as follows: # # * +:max_send_rate+ - Maximum number of emails you can send per second. - # * +:max_24_hour_send+ - Maximum number of emails you can send in a + # * +:max_24_hour_send+ - Maximum number of emails you can send in a # 24-hour period. # # To get your current quotas (and how many emails you have sent in the last @@ -194,17 +194,17 @@ def identities IdentityCollection.new(:config => config) end - # Sends an email. + # Sends an email. # # ses.send_email( # :subject => 'A Sample Email', - # :to => 'john@doe.com', + # :to => 'john@doe.com', # :from => 'no@reply.com', # :body_text => 'sample text ...', # :body_html => '

        sample text ...

        ') # - # You can also pass multiple email addresses for the +:to+, +:cc+, - # +:bcc+ and +:reply_to+ options. Email addresses can also be + # You can also pass multiple email addresses for the +:to+, +:cc+, + # +:bcc+ and +:reply_to+ options. Email addresses can also be # formatted with names. # # ses.send_email( @@ -224,9 +224,9 @@ def identities # @option options [String,Array] :bcc The address(es) to bcc (blind # carbon copy) the email to. # @option options [String,Array] :reply_to The reply-to email address(es) - # for the message. If the recipient replies to the message, each + # for the message. If the recipient replies to the message, each # reply-to address will receive the reply. - # @option options [String] :return_path The email address to which + # @option options [String] :return_path The email address to which # bounce notifications are to be forwarded. If the message cannot be # delivered to the recipient, then an error message will be returned # from the recipient's ISP; this message will then be forwarded to @@ -235,17 +235,17 @@ def identities # You must provide +:body_text+, +:body_html+ or both. # @option options [String] :body_html The email html contents. # You must provide +:body_text+, +:body_html+ or both. - # @option options [String] :subject_charset The character set of the + # @option options [String] :subject_charset The character set of the # +:subject+ string. If the text must contain any other characters, - # then you must also specify the character set. Examples include + # then you must also specify the character set. Examples include # UTF-8, ISO-8859-1, and Shift_JIS. Defaults to 7-bit ASCII. # @option options [String] :body_text_charset The character set of the # +:body_text+ string. If the text must contain any other characters, - # then you must also specify the character set. Examples include + # then you must also specify the character set. Examples include # UTF-8, ISO-8859-1, and Shift_JIS. Defaults to 7-bit ASCII. # @option options [String] :body_html_charset The character set of the # +:body_html+ string. If the text must contain any other characters, - # then you must also specify the character set. Examples include + # then you must also specify the character set. Examples include # UTF-8, ISO-8859-1, and Shift_JIS. Defaults to 7-bit ASCII. # @option options [String] :body_html # @return [nil] @@ -283,9 +283,9 @@ def send_email options = {} end - # Sends a raw email (email message, with header and content specified). + # Sends a raw email (email message, with header and content specified). # Useful for sending multipart MIME emails. The raw text of the message - # must comply with Internet email standards; otherwise, the message + # must comply with Internet email standards; otherwise, the message # cannot be sent. # # raw = <<-EMAIL @@ -304,28 +304,28 @@ def send_email options = {} # # ses.send_raw_email(raw) # - # Amazon SES has a limit on the total number of recipients per - # message: The combined number of To:, CC: and BCC: email addresses - # cannot exceed 50. If you need to send an email message to a larger - # audience, you can divide your recipient list into groups of 50 or - # fewer, and then call Amazon SES repeatedly to send the message to + # Amazon SES has a limit on the total number of recipients per + # message: The combined number of To:, CC: and BCC: email addresses + # cannot exceed 50. If you need to send an email message to a larger + # audience, you can divide your recipient list into groups of 50 or + # fewer, and then call Amazon SES repeatedly to send the message to # each group. # - # @param [required, String] raw_message The raw text of the message. + # @param [required, String] raw_message The raw text of the message. # You can pass in any object whos #to_s returns a valid formatted # email (e.g. ruby Mail gem). The raw message should: # * Contain a header and a body, separated by a blank line # * Contain all required internet email headers # * Each part of a multipart MIME message must be formatted properly - # * MIME content types must be among those supported by Amazon SES. + # * MIME content types must be among those supported by Amazon SES. # Refer to the Amazon SES Developer Guide for more details. # * Use content that is base64-encoded, if MIME requires it # @option options [String,Array] :to One or more email addresses to # send the email to. - # @option options [String] :from The sender's email address. - # If you specify the :from option, then bounce notifications and - # complaints will be sent to this email address. This takes - # precedence over any Return-Path header that you might include in + # @option options [String] :from The sender's email address. + # If you specify the :from option, then bounce notifications and + # complaints will be sent to this email address. This takes + # precedence over any Return-Path header that you might include in # the +raw_message+. # @return [nil] def send_raw_email raw_message, options = {} @@ -359,7 +359,7 @@ def quotas Quotas.new(:config => config).to_h end - # Returns an array of email statistics. Each object in this array is a + # Returns an array of email statistics. Each object in this array is a # hash with the following keys: # # * +:delivery_attempts+ @@ -387,7 +387,7 @@ def statistics def require_one_of options, *keys unless keys.any?{|key| options[key] } parts = keys.collect{|key| ":#{key}" }.join(', ') - raise ArgumentError, "you must provide at least one of #{parts}" + raise ArgumentError, "you must provide at least one of #{parts}" end end diff --git a/lib/aws/simple_email_service/email_address_collection.rb b/lib/aws/simple_email_service/email_address_collection.rb index c4e54b9388f..523b0331b33 100644 --- a/lib/aws/simple_email_service/email_address_collection.rb +++ b/lib/aws/simple_email_service/email_address_collection.rb @@ -15,7 +15,7 @@ module AWS class SimpleEmailService # Helps you manage your verified SimpleEmailService email addresses. - # @note This class is deprecated. Please use + # @note This class is deprecated. Please use # {SimpleEmailService#identities} instead. class EmailAddressCollection diff --git a/lib/aws/simple_email_service/identity.rb b/lib/aws/simple_email_service/identity.rb index d5d21b890d4..f50e21cca14 100644 --- a/lib/aws/simple_email_service/identity.rb +++ b/lib/aws/simple_email_service/identity.rb @@ -30,7 +30,7 @@ class SimpleEmailService # be enabled for email sent from this identity. # # @attr_reader [Array] dkim_tokens Returns a set of DNS records, - # or tokens, that must be published in the domain name's DNS to + # or tokens, that must be published in the domain name's DNS to # complete the DKIM verification process. Call {#verify_dkim} if this # returns an empty list. # diff --git a/lib/aws/simple_email_service/quotas.rb b/lib/aws/simple_email_service/quotas.rb index a0013c49ba0..2ac79e12cc8 100644 --- a/lib/aws/simple_email_service/quotas.rb +++ b/lib/aws/simple_email_service/quotas.rb @@ -25,13 +25,13 @@ def max_24_hour_send to_h[:max_24_hour_send] end - # @return [Float] The maximum number of emails the user is allowed + # @return [Float] The maximum number of emails the user is allowed # to send per second. def max_send_rate to_h[:max_send_rate] end - # @return [Integer] Returns the number of emails sent during the + # @return [Integer] Returns the number of emails sent during the # previous 24 hours. def sent_last_24_hours to_h[:sent_last_24_hours] @@ -42,14 +42,14 @@ def sent_last_24_hours # @ses.quotas.to_hash # # {:max_24_hour_send=>200, :max_send_rate=>1.0, :sent_last_24_hours=>22} # - # @return [Hash] + # @return [Hash] # def to_hash response = client.get_send_quota { :max_24_hour_send => response.max_24_hour_send.to_i, :max_send_rate => response.max_send_rate.to_f, - :sent_last_24_hours => response.sent_last_24_hours.to_i, + :sent_last_24_hours => response.sent_last_24_hours.to_i, } end alias_method :to_h, :to_hash @@ -58,7 +58,7 @@ def to_hash def inspect "<#{self.class} #{to_hash.inspect}>" end - + end end end diff --git a/lib/aws/simple_workflow.rb b/lib/aws/simple_workflow.rb index 423dddb3ccd..5433abd09d2 100644 --- a/lib/aws/simple_workflow.rb +++ b/lib/aws/simple_workflow.rb @@ -20,8 +20,8 @@ module AWS # # = Domains # - # To get started, you need to first create a domain. Domains are used to - # organize related tasks and activities. + # To get started, you need to first create a domain. Domains are used to + # organize related tasks and activities. # # swf = AWS::SimpleWorkflow.new # @@ -52,9 +52,9 @@ module AWS # :default_child_policy => :request_cancel, # :default_task_start_to_close_timeout => 3600, # :default_execution_start_to_close_timeout => 24 * 3600) - # + # # # register an activity type, with the version id '1' - # activity_type = domain.activity_types.create('do-something', '1', + # activity_type = domain.activity_types.create('do-something', '1', # :default_task_list => 'my-task-list', # :default_task_heartbeat_timeout => 900, # :default_task_schedule_to_start_timeout => 60, @@ -66,7 +66,7 @@ module AWS # Once you have a domain and at least one workflow type you can # start a workflow execution. You may provide a workflow id, or a # random one will be generated. You may also provide optional - # input and override any of the defaults registered with the + # input and override any of the defaults registered with the # workflow type. # # workflow_execution = workflow_type.start_execution :input => '...' @@ -81,7 +81,7 @@ module AWS # Yielded decision tasks provide access to the history of events # for the workflow execution. You can also enumerate only new # events since the last decision. - # + # # To make decisions you call methods from the list below. You can call # any number of decision methods any number of times. # @@ -130,22 +130,22 @@ module AWS # domain.activity_tasks.poll('my-task-list') do |activity_task| # # case activity_task.activity_type.name - # when 'do-something' + # when 'do-something' # # ... # else # activity_task.fail! :reason => 'unknown activity task type' # end - # + # # end # # == Activity Task Heartbeats # # When you receive an activity task, you need to update the service # with status messages. This is called recording a heartbeat.# - # To record a heartbeat, just call {ActivityTask#record_heartbeat!}. - # When you call +record_heartbeat+ you should rescue + # To record a heartbeat, just call {ActivityTask#record_heartbeat!}. + # When you call +record_heartbeat+ you should rescue # {ActivityTask::CancelRequestedError}. These are thrown when a task - # should be canceled. You can cleanup the task and then call + # should be canceled. You can cleanup the task and then call # +cancel!+ when you are finished. # # # poll 'my-task-list' for activities @@ -171,8 +171,8 @@ module AWS # activity_task.cancel! # end # end - # - # Like decision tasks, activity tasks are auto-completed at the + # + # Like decision tasks, activity tasks are auto-completed at the # end of a poll block. # # = History Events diff --git a/lib/aws/simple_workflow/activity_task.rb b/lib/aws/simple_workflow/activity_task.rb index 379d2574b7b..dd119d4d0bd 100644 --- a/lib/aws/simple_workflow/activity_task.rb +++ b/lib/aws/simple_workflow/activity_task.rb @@ -43,7 +43,7 @@ def initialize domain, data, options = {} end - # @return [String] The opaque string used as a handle on the task. + # @return [String] The opaque string used as a handle on the task. attr_reader :task_token # @return [String] The unique identifier of this task. @@ -52,11 +52,11 @@ def initialize domain, data, options = {} # @return [Domain] The domain this task was scheduled in. attr_reader :domain - # @return [Integer] The id of the ActivityTaskStarted event recorded + # @return [Integer] The id of the ActivityTaskStarted event recorded # in the history. attr_reader :started_event_id - # @return [String,nil] The input provided when the activity task was + # @return [String,nil] The input provided when the activity task was # scheduled. attr_reader :input @@ -79,7 +79,7 @@ def initialize domain, data, options = {} # # If you are processing the activity task inside a block passed # to one of the polling methods in {ActivityTaskCollection} - # then untrapped CancelRequestedErrors are caught + # then untrapped CancelRequestedErrors are caught # and responded to automatically. # # domain.activity_tasks.poll('task-list') do |task| @@ -103,7 +103,7 @@ def initialize domain, data, options = {} # task.respond_canceled! :details => '...' # # end - # + # # @param [Hash] options # # @option options [String] :details (nil) diff --git a/lib/aws/simple_workflow/activity_task_collection.rb b/lib/aws/simple_workflow/activity_task_collection.rb index 97d28eabdf9..85384048ed5 100644 --- a/lib/aws/simple_workflow/activity_task_collection.rb +++ b/lib/aws/simple_workflow/activity_task_collection.rb @@ -36,7 +36,7 @@ def initialize domain, options = {} # count.truncated? #=> false # count.to_i #=> 7 # - # @note This operation is eventually consistent. The results are best + # @note This operation is eventually consistent. The results are best # effort and may not exactly reflect recent updates and changes. # # @param [String] task_list The name of the task list. @@ -57,16 +57,16 @@ def count task_list # # @param [Hash] options # - # @option options [String] :identity (nil) Identity of the worker - # making the request, which is recorded in the ActivityTaskStarted - # event in the workflow history. This enables diagnostic tracing - # when problems arise. The :identity defaults to the hostname and + # @option options [String] :identity (nil) Identity of the worker + # making the request, which is recorded in the ActivityTaskStarted + # event in the workflow history. This enables diagnostic tracing + # when problems arise. The :identity defaults to the hostname and # pid (e.g. "hostname:pid"). # # @yieldparam [ActivityTask] activity_task Yields if a task is - # available within 60 seconds. + # available within 60 seconds. # - # @return [ActivityTask,nil] Returns an activity task when one is + # @return [ActivityTask,nil] Returns an activity task when one is # available, +nil+ otherwise. If you call this function with # a block, +nil+ is always returned. # @@ -106,7 +106,7 @@ def poll_for_single_task task_list, options = {}, &block end def poll task_list, options = {}, &block - loop do + loop do begin poll_for_single_task(task_list, options) do |activity_task| yield(activity_task) diff --git a/lib/aws/simple_workflow/activity_type.rb b/lib/aws/simple_workflow/activity_type.rb index 74a174177dd..2b10db8a109 100644 --- a/lib/aws/simple_workflow/activity_type.rb +++ b/lib/aws/simple_workflow/activity_type.rb @@ -16,7 +16,7 @@ class SimpleWorkflow # == Registering an ActivityType # - # To register an activity type you should use the #activity_types method + # To register an activity type you should use the #activity_types method # on the domain: # # domain.activity_types.register('name', 'version', { ... }) @@ -25,7 +25,7 @@ class SimpleWorkflow # # == Deprecating an activity type # - # ActivityType inherits from the generic {Type} base class. Defined in + # ActivityType inherits from the generic {Type} base class. Defined in # {Type} are a few useful methods including: # # * {Type#deprecate} @@ -47,31 +47,31 @@ class SimpleWorkflow # status will either be +:registered+ or +:deprecated+. # # @attr_reader [Integer,:none,nil] default_task_heartbeat_timeout - # The default maximum time specified when registering the activity - # type, before which a worker processing a task must report - # progress. If the timeout is exceeded, the activity task is - # automatically timed out. If the worker subsequently attempts - # to record a heartbeat or return a result, it will be ignored. + # The default maximum time specified when registering the activity + # type, before which a worker processing a task must report + # progress. If the timeout is exceeded, the activity task is + # automatically timed out. If the worker subsequently attempts + # to record a heartbeat or return a result, it will be ignored. # # The return value may be an integer (number of seconds), the # symbol +:none+ (implying no timeout) or +nil+ (not specified). # # @attr_reader [String,nil] default_task_list - # The default task list specified for this activity type at - # registration. This default task list is used if a task list is + # The default task list specified for this activity type at + # registration. This default task list is used if a task list is # not provided when a task is scheduled. # # @attr_reader [Integer,:none,nil] default_task_schedule_to_close_timeout - # The default maximum duration specified when registering the - # activity type, for tasks of this activity type. You can override + # The default maximum duration specified when registering the + # activity type, for tasks of this activity type. You can override # this default when scheduling a task. # # The return value may be an integer (number of seconds), the # symbol +:none+ (implying no timeout) or +nil+ (not specified). # # @attr_reader [Integer,:none,nil] default_task_schedule_to_start_timeout - # The optional default maximum duration specified when registering - # the activity type, that a task of an activity type can wait + # The optional default maximum duration specified when registering + # the activity type, that a task of an activity type can wait # before being assigned to a worker. # # The return value may be an integer (number of seconds), the @@ -109,7 +109,7 @@ class ActivityType < Type provider(:list_activity_types) do |provider| provider.provides *type_attributes.keys provider.find do |resp| - desc = resp.data['typeInfos'].find do |info| + desc = resp.data['typeInfos'].find do |info| info[self.class.type_key] == { 'name' => name, 'version' => version } end end diff --git a/lib/aws/simple_workflow/activity_type_collection.rb b/lib/aws/simple_workflow/activity_type_collection.rb index 2fd477b07a1..bed5b658045 100644 --- a/lib/aws/simple_workflow/activity_type_collection.rb +++ b/lib/aws/simple_workflow/activity_type_collection.rb @@ -15,31 +15,31 @@ module AWS class SimpleWorkflow class ActivityTypeCollection < TypeCollection - # Registers a new activity type along with its configuration settings + # Registers a new activity type along with its configuration settings # in the current domain. - # + # # @param [String] name The name of the activity type. # # @param [String] version The version of the activity type. - # The activity type consists of the name and version, the - # combination of which must be unique within the domain. + # The activity type consists of the name and version, the + # combination of which must be unique within the domain. # # @param [Hash] options - # + # # @option options [Integer,:none] :default_task_heartbeat_timeout (nil) - # The default maximum time before which a worker processing a task - # of this type must report progress. If the timeout is exceeded, - # the activity task is automatically timed out. If the worker - # subsequently attempts to record a heartbeat or returns a - # result, it will be ignored. This default can be overridden when + # The default maximum time before which a worker processing a task + # of this type must report progress. If the timeout is exceeded, + # the activity task is automatically timed out. If the worker + # subsequently attempts to record a heartbeat or returns a + # result, it will be ignored. This default can be overridden when # scheduling an activity task. # # The value should be a number of seconds (integer) or the symbol # +:none+ (implying no timeout). # - # @option options [String] :default_task_list (nil) The default task - # list to use for scheduling tasks of this activity type. - # This default task list is used if a task list is not provided + # @option options [String] :default_task_list (nil) The default task + # list to use for scheduling tasks of this activity type. + # This default task list is used if a task list is not provided # when a task is scheduled. # # @option options [Integer,:none] :default_task_schedule_to_close_timeout (nil) @@ -47,22 +47,22 @@ class ActivityTypeCollection < TypeCollection # +:none+ (implying no timeout). # # @option options [Integer,:none] :default_task_schedule_to_start_timeout (nil) - # The default maximum duration that a task of this activity type - # can wait before being assigned to a worker. This default can be + # The default maximum duration that a task of this activity type + # can wait before being assigned to a worker. This default can be # overridden when scheduling an activity task. # # The value should be a number of seconds (integer) or the symbol # +:none+ (implying no timeout). # # @option options [Integer,:none] :default_task_start_to_close_timeout (nil) - # The default maximum duration that a worker can take to process + # The default maximum duration that a worker can take to process # tasks of this activity type (in the ISO 8601 format). This default # can be overridden when scheduling an activity task. # # The value should be a number of seconds (integer) or the symbol # +:none+ (implying no timeout). # - # @option options [String] :description (nil) A textual description + # @option options [String] :description (nil) A textual description # of the activity type. # def register name, version, options = {} @@ -71,7 +71,7 @@ def register name, version, options = {} options[:name] = name options[:version] = version - duration_opts(options, + duration_opts(options, :default_task_heartbeat_timeout, :default_task_schedule_to_close_timeout, :default_task_schedule_to_start_timeout, @@ -84,7 +84,7 @@ def register name, version, options = {} client.register_activity_type(options) self[name, version] - + end alias_method :create, :register diff --git a/lib/aws/simple_workflow/count.rb b/lib/aws/simple_workflow/count.rb index f10340aa634..0f0a7a48f68 100644 --- a/lib/aws/simple_workflow/count.rb +++ b/lib/aws/simple_workflow/count.rb @@ -14,9 +14,9 @@ module AWS class SimpleWorkflow - # Simple Workflow returns counts that may be truncated. Truncated - # counts indicate a lower bound. A count of 100 that is truncated - # could be represented to a user like "100+". Non-truncated counts + # Simple Workflow returns counts that may be truncated. Truncated + # counts indicate a lower bound. A count of 100 that is truncated + # could be represented to a user like "100+". Non-truncated counts # are definitive. class Count @@ -37,8 +37,8 @@ def truncated? end def == other - other.is_a?(Count) and - other.count == self.count and + other.is_a?(Count) and + other.count == self.count and other.truncated? == self.truncated? end diff --git a/lib/aws/simple_workflow/decision_task.rb b/lib/aws/simple_workflow/decision_task.rb index 9a62a950d6c..5466f52e549 100644 --- a/lib/aws/simple_workflow/decision_task.rb +++ b/lib/aws/simple_workflow/decision_task.rb @@ -30,7 +30,7 @@ class SimpleWorkflow # # == Exploring Event History # - # Once you have a decision task you can examine the event history. + # Once you have a decision task you can examine the event history. # This can give you the information you need to make decisions. # The {#events} and {#new_events} methods enumerate through # all events or events since the last decision. @@ -38,7 +38,7 @@ class SimpleWorkflow # decision_task.new_events.each do |event| # # inspect the #event_type and #attributes # end - # + # # Check out {HistoryEvent} for more information on working with # events. # @@ -47,10 +47,10 @@ class SimpleWorkflow # Based on the history of events, you should make decisions by calling # methods listed below. You can call each method as many times as # you wish, until you have completed the decision task. - # + # # * {#schedule_activity_task} # * {#request_cancel_activity_task} - # + # # * {#complete_workflow_execution} # * {#cancel_workflow_execution} # * {#fail_workflow_execution} @@ -69,7 +69,7 @@ class SimpleWorkflow # == Completing the Decision Task # # Once you have finished adding decisions to the task, you need to - # complete it. If you called {DecisionTaskCollection#poll} or + # complete it. If you called {DecisionTaskCollection#poll} or # {DecisionTaskCollection#poll_for_single_task} with a block # argument then the decision will be completed automatically at the # end of the block. @@ -81,8 +81,8 @@ class SimpleWorkflow # If you get a task from {DecisionTaskCollection#poll_for_single_task} # without a block, then it is your responsibility to call {#complete!} # on the decision task. If you fail to do this before the - # task start to close timeout, then a decisionTaskTimedOut event - # will be added to the workflow execution history. + # task start to close timeout, then a decisionTaskTimedOut event + # will be added to the workflow execution history. # class DecisionTask @@ -113,7 +113,7 @@ def initialize domain, request_options, data @next_token = data['nextPageToken'] @events = data['events'] - + @decisions = [] super @@ -132,18 +132,18 @@ def initialize domain, request_options, data # @return [WorkflowType] attr_reader :workflow_type - # @return [Integer] The id of the DecisionTaskStarted event of the - # previous decision task of this workflow execution that was - # processed by the decider. This can be used to determine the new - # events in the history new since the last decision task received + # @return [Integer] The id of the DecisionTaskStarted event of the + # previous decision task of this workflow execution that was + # processed by the decider. This can be used to determine the new + # events in the history new since the last decision task received # by the decider. attr_reader :previous_started_event_id - # @return [Integer] The id of the DecisionTaskStarted event recorded + # @return [Integer] The id of the DecisionTaskStarted event recorded # in the history. attr_reader :started_event_id - # @return [String,nil] Returns a value if the results are paginated. + # @return [String,nil] Returns a value if the results are paginated. # Normally you do not need this value, as {#events} will enumerate # all events, making requests as necessary to get more. attr_reader :next_token @@ -156,14 +156,14 @@ def events # @return [Enumerable] Returns an enumerable collection of only the # new events for workflow execution (since the last decision). - def new_events + def new_events enum_for(:_new_events) end # @param [Hash] options # - # @option options [String] :execution_context (nil) A user-defined - # context to add to the workflow execution. You can fetch this + # @option options [String] :execution_context (nil) A user-defined + # context to add to the workflow execution. You can fetch this # later with {WorkflowExecution#latest_execution_context}. # # @return [nil] @@ -188,7 +188,7 @@ def complete! options = {} def responded? !!@responded end - + # Schedules an activity task. # # @note This adds a decision to this task that is finalized when you @@ -200,55 +200,55 @@ def responded? # # @param [Hash] options # - # @option options [String] :control (nil) Optional data attached to - # the event that can be used by the decider in subsequent workflow + # @option options [String] :control (nil) Optional data attached to + # the event that can be used by the decider in subsequent workflow # tasks. This data is not sent to the activity. # # @option options [Integer,:none] :heartbeat_timeout (nil) - # The maximum time before which a worker processing a task - # of this type must report progress. If the timeout is exceeded, - # the activity task is automatically timed out. If the worker - # subsequently attempts to record a heartbeat or returns a - # result, it will be ignored. This default can be overridden when + # The maximum time before which a worker processing a task + # of this type must report progress. If the timeout is exceeded, + # the activity task is automatically timed out. If the worker + # subsequently attempts to record a heartbeat or returns a + # result, it will be ignored. This default can be overridden when # scheduling an activity task. # # The value should be a number of seconds (integer) or the symbol # +:none+ (implying no timeout). # - # @option options [String] :input (nil) Input provided to the + # @option options [String] :input (nil) Input provided to the # activity task. # - # @option options [Integer,:none] :schedule_to_close_timeout (nil) - # The maximum duration that a task of this activity type - # can wait before being assigned to a worker. + # @option options [Integer,:none] :schedule_to_close_timeout (nil) + # The maximum duration that a task of this activity type + # can wait before being assigned to a worker. # - # A schedule-to-close timeout for this activity task must be - # specified either as a default for the activity type or through - # this option. If neither this field is set nor a default was + # A schedule-to-close timeout for this activity task must be + # specified either as a default for the activity type or through + # this option. If neither this field is set nor a default was # specified at registration time then a fault will be returned. # # The value should be a number of seconds (integer) or the symbol # +:none+ (implying no timeout). # # @option options [Integer,:none] :schedule_to_start_timeout (nil) - # The maximum duration that a task of this activity type - # can wait before being assigned to a worker. This overrides the + # The maximum duration that a task of this activity type + # can wait before being assigned to a worker. This overrides the # default timeout specified when registering the activity type. # # The value should be a number of seconds (integer) or the symbol # +:none+ (implying no timeout). # # @option options [Integer,:none] :start_to_close_timeout (nil) - # The maximum duration that a worker can take to process - # tasks of this activity type. This overrides the default + # The maximum duration that a worker can take to process + # tasks of this activity type. This overrides the default # timeout specified when registering the activity type. # # The value should be a number of seconds (integer) or the symbol # +:none+ (implying no timeout). # # @option options [String] :task_list (nil) - # If set, specifies the name of the task list in which to schedule - # the activity task. If not specified, the default task list + # If set, specifies the name of the task list in which to schedule + # the activity task. If not specified, the default task list # registered with the activity type will be used. # # @return [nil] @@ -258,8 +258,8 @@ def schedule_activity_task activity_type, options = {} options[:activity_id] ||= UUIDTools::UUID.random_create.to_s options[:activity_type] = case activity_type - when Hash - unless + when Hash + unless activity_type[:name].is_a?(String) and activity_type[:version].is_a?(String) and activity_type.keys.length == 2 @@ -272,10 +272,10 @@ def schedule_activity_task activity_type, options = {} { :name => activity_type.name, :version => activity_type.version } else msg = 'expected activity_type to be an ActivityType object or a hash' - raise ArgumentError, msg + raise ArgumentError, msg end - duration_opts(options, + duration_opts(options, :heartbeat_timeout, :schedule_to_close_timeout, :schedule_to_start_timeout, @@ -286,17 +286,17 @@ def schedule_activity_task activity_type, options = {} end add_decision :schedule_activity_task, options - + end - # Attempts to cancel a previously scheduled activity task. If the - # activity task was scheduled but has not been assigned to a worker, - # then it will be canceled. If the activity task was already assigned - # to a worker, then the worker will be informed that cancellation has + # Attempts to cancel a previously scheduled activity task. If the + # activity task was scheduled but has not been assigned to a worker, + # then it will be canceled. If the activity task was already assigned + # to a worker, then the worker will be informed that cancellation has # been requested when recording the activity task heartbeat. # # @param [ActivityTask,String] activity_or_activity_id An {ActivityTask} - # object or the activity_id of an activity task to request + # object or the activity_id of an activity task to request # cancellation for. # # @return [nil] @@ -311,12 +311,12 @@ def request_cancel_activity_task activity_or_activity_id end - # Closes the workflow execution and records a + # Closes the workflow execution and records a # WorkflowExecutionCompleted event in the history. # # @param [Hash] options # - # @option options [String] :result (nil) The results of the workflow + # @option options [String] :result (nil) The results of the workflow # execution. # # @return [nil] @@ -325,7 +325,7 @@ def complete_workflow_execution options = {} add_decision :complete_workflow_execution, options end - # Closes the workflow execution and records a WorkflowExecutionFailed + # Closes the workflow execution and records a WorkflowExecutionFailed # event in the history. # # @param [Hash] options @@ -340,12 +340,12 @@ def fail_workflow_execution options = {} add_decision :fail_workflow_execution, options end - # Closes the workflow execution and records a + # Closes the workflow execution and records a # WorkflowExecutionCanceled event in the history. # # @param [Hash] options - # - # @option options [String] :details (nil) Optional details of the + # + # @option options [String] :details (nil) Optional details of the # cancellation. # # @return [nil] @@ -354,57 +354,57 @@ def cancel_workflow_execution options = {} add_decision :cancel_workflow_execution, options end - # Closes the workflow execution and starts a new workflow execution - # of the same type using the same workflow id and a unique run Id. + # Closes the workflow execution and starts a new workflow execution + # of the same type using the same workflow id and a unique run Id. # A WorkflowExecutionContinuedAsNew event is recorded in the history. # # @option options [String] :input (nil) - # The input for the workflow execution. This is a free form string - # which should be meaningful to the workflow you are starting. - # This input is made available to the new workflow execution in the + # The input for the workflow execution. This is a free form string + # which should be meaningful to the workflow you are starting. + # This input is made available to the new workflow execution in the # WorkflowExecutionStarted history event. - # + # # @option options [Array] :tag_list ([]) # A list of tags (strings) to associate with the workflow execution. # You can specify a maximum of 5 tags. # # @option options [Symbol] :child_policy (nil) - # Specifies the policy to use for the child workflow executions of - # this workflow execution if it is terminated explicitly or due to - # an expired timeout. This policy overrides the default child policy + # Specifies the policy to use for the child workflow executions of + # this workflow execution if it is terminated explicitly or due to + # an expired timeout. This policy overrides the default child policy # specified when registering the workflow type. The supported child # policies are: # # * +:terminate+ - the child executions will be terminated. # # * +:request_cancel+ - a request to cancel will be attempted for each - # child execution by recording a WorkflowExecutionCancelRequested + # child execution by recording a WorkflowExecutionCancelRequested # event in its history. It is up to the decider to take appropriate # actions when it receives an execution history with this event. # - # * +:abandon+ - no action will be taken. The child executions will + # * +:abandon+ - no action will be taken. The child executions will # continue to run. # # @option options [Integer,:none] :execution_start_to_close_timeout (nil) - # The total duration for this workflow execution. This overrides + # The total duration for this workflow execution. This overrides # the default specified when registering the workflow type. # # The value should be a number of seconds (integer) or the symbol # +:none+ (implying no timeout). # - # @option options [String] :task_list (nil) - # The task list to use for the decision tasks generated for this + # @option options [String] :task_list (nil) + # The task list to use for the decision tasks generated for this # workflow execution. This overrides the default task list specified # when registering the workflow type. # # @option options [Integer,:none] :task_start_to_close_timeout (nil) - # Specifies the maximum duration of decision tasks for this - # workflow execution. This parameter overrides the default + # Specifies the maximum duration of decision tasks for this + # workflow execution. This parameter overrides the default # specified when the workflow type was registered. # # The value should be a number of seconds (integer) or the symbol # +:none+ (implying no timeout). - # + # # @return [nil] # def continue_as_new_workflow_execution options = {} @@ -412,9 +412,9 @@ def continue_as_new_workflow_execution options = {} add_decision :continue_as_new_workflow_execution, options end - # Records a MarkerRecorded event in the history. Markers can be used - # for adding custom information in the history for instance to let - # deciders know that they do not need to look at the history beyond + # Records a MarkerRecorded event in the history. Markers can be used + # for adding custom information in the history for instance to let + # deciders know that they do not need to look at the history beyond # the marker event. # # @param [String] marker_name The name of the marker. @@ -428,21 +428,21 @@ def continue_as_new_workflow_execution options = {} def record_marker marker_name, options = {} add_decision :record_marker, options.merge(:marker_name => marker_name) end - - # Schedules a timer for this workflow execution and records a - # TimerScheduled event in the history. This timer will fire after + + # Schedules a timer for this workflow execution and records a + # TimerScheduled event in the history. This timer will fire after # the specified delay and record a TimerFired event. # - # @param [Integer] start_to_fire_timeout (nil) The duration to wait - # before firing the timer. - # + # @param [Integer] start_to_fire_timeout (nil) The duration to wait + # before firing the timer. + # # @param [Hash] options # - # @option options [String] :control (nil) Optional data attached to - # the event that can be used by the decider in subsequent workflow + # @option options [String] :control (nil) Optional data attached to + # the event that can be used by the decider in subsequent workflow # tasks. # - # @option options [String] :timer_id (nil) Unique id for the timer. + # @option options [String] :timer_id (nil) Unique id for the timer. # If you do not pass this option, a UUID will be generated. # # @return [String] Returns the id of the timer. @@ -467,7 +467,7 @@ def cancel_timer timer_id end # Requests a signal to be delivered to the specified external workflow - # execution and records a SignalExternalWorkflowExecutionRequested + # execution and records a SignalExternalWorkflowExecutionRequested # event in the history. # # @param [WorkflowExecution,String] workflow_execution @@ -476,13 +476,13 @@ def cancel_timer timer_id # # @param [Hash] options # - # @option options [String] :control (nil) Optional data attached to - # the event that can be used by the decider in subsequent decision + # @option options [String] :control (nil) Optional data attached to + # the event that can be used by the decider in subsequent decision # tasks. # - # @option options [String] :input (nil) Optional input to be provided - # with the signal. The target workflow execution will use the - # signal name and input to process the signal. + # @option options [String] :input (nil) Optional input to be provided + # with the signal. The target workflow execution will use the + # signal name and input to process the signal. # # @return [nil] # @@ -492,9 +492,9 @@ def signal_external_workflow_execution workflow_execution, signal_name, options add_decision :signal_external_workflow_execution, options end - # Requests that a request be made to cancel the specified external - # workflow execution and records a - # RequestCancelExternalWorkflowExecutionRequested event in + # Requests that a request be made to cancel the specified external + # workflow execution and records a + # RequestCancelExternalWorkflowExecutionRequested event in # the history. # # @return [nil] @@ -504,12 +504,12 @@ def request_cancel_external_workflow_execution workflow_execution, options = {} add_decision :request_cancel_external_workflow_execution, options end - # Requests that a child workflow execution be started and records a - # StartChildWorkflowExecutionRequested event in the history. - # The child workflow execution is a separate workflow execution with + # Requests that a child workflow execution be started and records a + # StartChildWorkflowExecutionRequested event in the history. + # The child workflow execution is a separate workflow execution with # its own history. # - # @param [WorkflowType,Hash] workflow_type (nil) The type of + # @param [WorkflowType,Hash] workflow_type (nil) The type of # workflow execution to start. This should be a {WorkflowType} object # or a hash with the keys +:name+ and +:version+. # @@ -517,8 +517,8 @@ def request_cancel_external_workflow_execution workflow_execution, options = {} # # @option (see WorkflowType#start_execution) # - # @option options [String] :control (nil) Optional data attached to - # the event that can be used by the decider in subsequent workflow + # @option options [String] :control (nil) Optional data attached to + # the event that can be used by the decider in subsequent workflow # tasks. # # @return [String] Returns the workflow id of the new execution. @@ -535,8 +535,8 @@ def workflow_execution_opts options, workflow_execution if workflow_execution.is_a?(WorkflowExecution) options[:workflow_id] = workflow_execution.workflow_id options[:run_id] = workflow_execution.run_id - elsif - workflow_execution.is_a?(Hash) and + elsif + workflow_execution.is_a?(Hash) and workflow_execution[:workflow_id].is_a?(String) and workflow_execution[:run_id].is_a?(String) and workflow_execution.keys.length == 2 @@ -554,9 +554,9 @@ def workflow_execution_opts options, workflow_execution protected def add_decision decision_type, attributes - @decisions << { + @decisions << { :decision_type => Core::Inflection.class_name(decision_type.to_s), - :"#{decision_type}_decision_attributes" => attributes, + :"#{decision_type}_decision_attributes" => attributes, } nil end @@ -591,7 +591,7 @@ def _each_event events, &block events.each do |description| event = HistoryEvent.new(workflow_execution, description) Core::MetaUtils.extend_method(event, :new?) do - prev_event_id.nil? or self.event_id > prev_event_id + prev_event_id.nil? or self.event_id > prev_event_id end yield(event) end diff --git a/lib/aws/simple_workflow/decision_task_collection.rb b/lib/aws/simple_workflow/decision_task_collection.rb index 1d434a727db..d6e6b9583f6 100644 --- a/lib/aws/simple_workflow/decision_task_collection.rb +++ b/lib/aws/simple_workflow/decision_task_collection.rb @@ -51,20 +51,20 @@ class SimpleWorkflow # You can poll indefinitely for tasks in a loop with {#poll}: # # domain.decision_tasks.poll('my-task-list') do |task| - # # yields once for every decision task found + # # yields once for every decision task found # end # # Just like the block form above, the decision task is auto completed at - # the end of the block. Please note, if you call +break+ or +return+ + # the end of the block. Please note, if you call +break+ or +return+ # from inside the block, you *MUST* call {DecisionTask#complete!} or # the task will timeout. # # == Events and Decisions # # Each decision task provides an enumerable collection of both - # new events ({DecisionTask#new_events}) and all events - # ({DecisionTask#events}). - # + # new events ({DecisionTask#new_events}) and all events + # ({DecisionTask#events}). + # # Based on the events in the workflow execution history, you should # call methods on the decision task. See {DecisionTask} for # a complete list of decision methods. @@ -83,16 +83,16 @@ def initialize domain, options = {} # @return [Domain] attr_reader :domain - # Returns the number of decision tasks in the specified +task_list+. + # Returns the number of decision tasks in the specified +task_list+. # # count = decision_tasks.count('task-list-name') # count.truncated? #=> false # count.to_i #=> 7 # - # @note This operation is eventually consistent. The results are best + # @note This operation is eventually consistent. The results are best # effort and may not exactly reflect recent updates and changes. # - # @param [String] task_list Name of the task list to count + # @param [String] task_list Name of the task list to count # decision tasks for. # # @return [Count] Returns a {Count} object that specifies the number @@ -108,7 +108,7 @@ def count task_list end # Polls the service for a single decision task. The service may - # hold the request for up to 60 seconds before responding. + # hold the request for up to 60 seconds before responding. # # # try to get a single task, may return nil when no tasks available # task = domain.decision_tasks.poll_for_single_task('task-list') @@ -125,27 +125,27 @@ def count task_list # # task.complete! is called for you at the end of the block # end # - # With the block form you do not need to call #complete! on the + # With the block form you do not need to call #complete! on the # decision task. It will be called when the block exists. # - # @note If you are not using the block form you must call + # @note If you are not using the block form you must call # {DecisionTask#complete!} yourself or your decision task will # timeout. # - # @param [String] task_list Specifies the task list to poll for + # @param [String] task_list Specifies the task list to poll for # decision tasks. # # @param [Hash] options # - # @option options [String] :identity The identity of the decider + # @option options [String] :identity The identity of the decider # requesting a decision task. This will be recorded in the - # DecisionTaskStarted event in the workflow history. - # If +:identity+ is not passed then the hostname and + # DecisionTaskStarted event in the workflow history. + # If +:identity+ is not passed then the hostname and # process id will be sent (e.g. "hostname:pid"). # - # @option options [Boolean] :reverse_event_order (false) When true, - # the history events on the decision task will enumerate in - # reverse chronological order (newest events first). By default + # @option options [Boolean] :reverse_event_order (false) When true, + # the history events on the decision task will enumerate in + # reverse chronological order (newest events first). By default # the events are enumerated in chronological order (oldest first). # # @option options [Integer] :event_batch_size (1000) When enumerating @@ -154,7 +154,7 @@ def count task_list # of events to request each time (must not be greater than 1000). # # @yieldparam [DecisionTask] decision_task - # + # # @return [DecisionTask,nil] Returns a decision task or +nil+. If # a block was passed then +nil+ is always returned. If a block # is not passed, then +nil+ or a {DecisionTask} will be returned. @@ -196,19 +196,19 @@ def poll_for_single_task task_list, options = {}, &block # end # # @note If you to terminate the block (by calling +break+ or +return+) - # then it is your responsibility to call #complete! on the decision + # then it is your responsibility to call #complete! on the decision # task. # # @param (see #poll_for_single_task) # # @option (see #poll_for_single_task) - # + # # @yieldparam [DecisionTask] decision_task # # @return [nil] # def poll task_list, options = {}, &block - loop do + loop do begin poll_for_single_task(task_list, options) do |decision_task| yield(decision_task) diff --git a/lib/aws/simple_workflow/domain.rb b/lib/aws/simple_workflow/domain.rb index 5bc9d7a5513..b1fb4ea297c 100644 --- a/lib/aws/simple_workflow/domain.rb +++ b/lib/aws/simple_workflow/domain.rb @@ -14,10 +14,10 @@ module AWS class SimpleWorkflow - # Domains are used to organize workflows types and activities for + # Domains are used to organize workflows types and activities for # an account. # - # @attr_reader [String,nil] description Returns + # @attr_reader [String,nil] description Returns # # @attr_reader [Integer,Symbol] retention_period Returns the retention # period for this domain. The return value may be an integer (number @@ -44,7 +44,7 @@ def initialize name, options = {} info_attribute :status, :to_sym => true - config_attribute :retention_period, + config_attribute :retention_period, :from => 'workflowExecutionRetentionPeriodInDays', :duration => true, :static => true @@ -79,15 +79,15 @@ def deprecated? self.status == :deprecated end - # Deprecates the domain. After a domain has been deprecated it cannot - # be used to create new workflow executions or register new types. - # However, you can still use visibility actions on this domain. + # Deprecates the domain. After a domain has been deprecated it cannot + # be used to create new workflow executions or register new types. + # However, you can still use visibility actions on this domain. # - # Deprecating a domain also deprecates all activity and workflow - # types registered in the domain. Executions that were started + # Deprecating a domain also deprecates all activity and workflow + # types registered in the domain. Executions that were started # before the domain was deprecated will continue to run. # - # @return [nil] + # @return [nil] # def deprecate client.deprecate_domain(:name => name) diff --git a/lib/aws/simple_workflow/domain_collection.rb b/lib/aws/simple_workflow/domain_collection.rb index 0706c76bdd1..d22336d1a2c 100644 --- a/lib/aws/simple_workflow/domain_collection.rb +++ b/lib/aws/simple_workflow/domain_collection.rb @@ -44,7 +44,7 @@ class SimpleWorkflow # # returns an array of names for all deprecated domains # simple_workflow.domains.deprecated.map(&:name) # - # See {AWS::Core::Collection} to see other useful methods you can + # See {AWS::Core::Collection} to see other useful methods you can # call against a domain collection (e.g. #enum, #page, #each_batch). # class DomainCollection @@ -53,8 +53,8 @@ class DomainCollection include Core::Collection::WithLimitAndNextToken def initialize options = {} - - @registration_status = options[:registration_status] ? + + @registration_status = options[:registration_status] ? options[:registration_status].to_s.upcase : 'REGISTERED' @reverse_order = options.key?(:reverse_order) ? @@ -70,19 +70,19 @@ def initialize options = {} # # execution history # domain = AWS::SimpleWorkflow.new.domains.register('domain', :none) # - # @param [String] name Name of the domain to register. The name must - # be unique. + # @param [String] name Name of the domain to register. The name must + # be unique. # - # @param [Integer,:none] retention_period A duration (in days) - # for which the record (including the history) of workflow - # executions in this domain should be kept by the service. - # After the retention period, the workflow execution will not be + # @param [Integer,:none] retention_period A duration (in days) + # for which the record (including the history) of workflow + # executions in this domain should be kept by the service. + # After the retention period, the workflow execution will not be # available in the results of visibility calls. # # If you pass the symbol +:none+ then there is no expiration for # workflow execution history (effectively an infinite retention # period). - # + # # @param [Hash] options # # @option [String] :description (nil) Textual description of the domain. @@ -146,7 +146,7 @@ def _each_item next_token, limit, options = {}, &block options[:maximum_page_size] = limit if limit options[:next_page_token] = next_token if next_token options[:registration_status] ||= @registration_status - options[:reverse_order] = @reverse_order unless + options[:reverse_order] = @reverse_order unless options.has_key?(:reverse_order) response = client.list_domains(options) @@ -156,7 +156,7 @@ def _each_item next_token, limit, options = {}, &block desc['name'], :config => config) yield(domain) - + end response.data['nextPageToken'] diff --git a/lib/aws/simple_workflow/history_event.rb b/lib/aws/simple_workflow/history_event.rb index b64270c30d8..9c7d1b0c904 100644 --- a/lib/aws/simple_workflow/history_event.rb +++ b/lib/aws/simple_workflow/history_event.rb @@ -18,7 +18,7 @@ class SimpleWorkflow # == Getting History Events # - # History events belong to workflow executions. You can get them + # History events belong to workflow executions. You can get them # from an execution two ways: # # 1) By enumerating events from the execution @@ -42,7 +42,7 @@ class SimpleWorkflow # * {#event_id} # * {#created_at} # * {#attributes} - # + # # For a complete list of event types and a complete list of attributes # returned with each event type, see the service API documentation. # @@ -55,7 +55,7 @@ class SimpleWorkflow # # See {HistoryEvent::Attributes} for more information about working # with the returned attributes. - # + # class HistoryEvent include Core::Model @@ -63,7 +63,7 @@ class HistoryEvent # @param [WorkflowExecution] workflow_execution # # @param [Hash,String] details A hash or JSON string describing - # the history event. + # the history event. # def initialize workflow_execution, details @@ -131,7 +131,7 @@ def inspect # # == Indifferent Access # - # Here are a few examples showing the different ways to access an + # Here are a few examples showing the different ways to access an # attribute: # # event = workflow_executions.events.first @@ -146,11 +146,11 @@ def inspect # snake_cased or camelCased (strings or symbols). # # == Special Attributes - # + # # The following list of attributes are treated specially. Generally this - # means they return + # means they return # - # * timeout attributes (e.g. taskStartToCloseTimeout) are returned as + # * timeout attributes (e.g. taskStartToCloseTimeout) are returned as # integers (number of seconds) or the special symbol :none, implying # there is no timeout. # @@ -165,7 +165,7 @@ def inspect # * taskList is returned as a string, not a hash. # class Attributes - + # @private def initialize workflow_execution, data @workflow_execution = workflow_execution @@ -184,7 +184,7 @@ def [] key end end - # @return [Array] Returns a list of valid keys for this + # @return [Array] Returns a list of valid keys for this # set of attributes. def keys @data.keys.collect{|key| _snake_case(key) } @@ -204,7 +204,7 @@ def method_missing method self[method] end - # @return [Hash] Returns all of the attributes in a hash with + # @return [Hash] Returns all of the attributes in a hash with # snaked_cased and symbolized keys. def to_h @data.inject({}) do |h,(key,value)| diff --git a/lib/aws/simple_workflow/history_event_collection.rb b/lib/aws/simple_workflow/history_event_collection.rb index e8954dfe717..5a0e4612d2d 100644 --- a/lib/aws/simple_workflow/history_event_collection.rb +++ b/lib/aws/simple_workflow/history_event_collection.rb @@ -14,7 +14,7 @@ module AWS class SimpleWorkflow - # This collection represents the history events ({HistoryEvent}) for a + # This collection represents the history events ({HistoryEvent}) for a # single workflow execution. # # See {Core::Collection} for documentation on the standard enumerable @@ -24,7 +24,7 @@ class HistoryEventCollection include Core::Collection::WithLimitAndNextToken - # @param [WorkflowExecution] workflow_execution The execution this + # @param [WorkflowExecution] workflow_execution The execution this # history event belongs to. # # @param [Hash] options @@ -58,7 +58,7 @@ def _each_item next_token, limit, options = {}, &block } options[:maximum_page_size] = limit if limit options[:next_page_token] = next_token if next_token - options[:reverse_order] = @reverse_order unless + options[:reverse_order] = @reverse_order unless options.has_key?(:reverse_order) response = client.get_workflow_execution_history(options) diff --git a/lib/aws/simple_workflow/option_formatters.rb b/lib/aws/simple_workflow/option_formatters.rb index 708740ad718..9420933dca5 100644 --- a/lib/aws/simple_workflow/option_formatters.rb +++ b/lib/aws/simple_workflow/option_formatters.rb @@ -15,7 +15,7 @@ module AWS class SimpleWorkflow - + # @private module OptionFormatters @@ -23,7 +23,7 @@ module OptionFormatters def identity_opt options options[:identity] || "#{Socket.gethostname}:#{Process.pid}" end - + protected def upcase_opts options, *opt_names opt_names.each do |opt| @@ -45,12 +45,12 @@ def start_execution_opts options, workflow_type = nil if workflow_type options[:workflow_id] ||= UUIDTools::UUID.random_create.to_s - + if workflow_type.is_a?(WorkflowType) options[:workflow_type] = {} options[:workflow_type][:name] = workflow_type.name options[:workflow_type][:version] = workflow_type.version - elsif + elsif workflow_type.is_a?(Hash) and workflow_type[:name].is_a?(String) and workflow_type[:version] .is_a?(String)and @@ -67,7 +67,7 @@ def start_execution_opts options, workflow_type = nil upcase_opts(options, :child_policy) - duration_opts(options, + duration_opts(options, :execution_start_to_close_timeout, :task_start_to_close_timeout) diff --git a/lib/aws/simple_workflow/request.rb b/lib/aws/simple_workflow/request.rb index 5b180c36833..dd185d2161d 100644 --- a/lib/aws/simple_workflow/request.rb +++ b/lib/aws/simple_workflow/request.rb @@ -22,7 +22,7 @@ class Request < Core::Http::Request def read_timeout # increase read timeout for long polling if headers['x-amz-target'] =~ /PollFor(Decision|Activity)Task/ - 90 + 90 else @read_timeout end diff --git a/lib/aws/simple_workflow/resource.rb b/lib/aws/simple_workflow/resource.rb index 287978ddbb5..937a6bee9a5 100644 --- a/lib/aws/simple_workflow/resource.rb +++ b/lib/aws/simple_workflow/resource.rb @@ -39,7 +39,7 @@ def self.attribute name, options = {}, &block if options[:duration] super(name, options) do - translates_output do |v| + translates_output do |v| v.to_s =~ /^\d+$/ ? v.to_i : v.downcase.to_sym end end diff --git a/lib/aws/simple_workflow/type.rb b/lib/aws/simple_workflow/type.rb index a1e748db484..1eb1c019454 100644 --- a/lib/aws/simple_workflow/type.rb +++ b/lib/aws/simple_workflow/type.rb @@ -36,15 +36,15 @@ def initialize domain, name, version, options = {} # @return [String] Returns the version of this type. attr_reader :version - # Deprecates the type. + # Deprecates the type. # - # After a type has been deprecated, you cannot create new - # executions of that type. Executions that were started before the + # After a type has been deprecated, you cannot create new + # executions of that type. Executions that were started before the # type was deprecated will continue to run. # - # @note This operation is eventually consistent. The results are best + # @note This operation is eventually consistent. The results are best # effort and may not exactly reflect recent updates and changes. - # + # # @return [nil] # def deprecate @@ -66,10 +66,10 @@ def resource_identifiers protected def resource_options { - :domain => domain.name, - :"#{self.class.ruby_name}" => { + :domain => domain.name, + :"#{self.class.ruby_name}" => { :name => name, - :version => version + :version => version } } end diff --git a/lib/aws/simple_workflow/type_collection.rb b/lib/aws/simple_workflow/type_collection.rb index c897a570a75..94efa46695a 100644 --- a/lib/aws/simple_workflow/type_collection.rb +++ b/lib/aws/simple_workflow/type_collection.rb @@ -19,9 +19,9 @@ class SimpleWorkflow class TypeCollection include OptionFormatters - include Core::Collection::WithLimitAndNextToken + include Core::Collection::WithLimitAndNextToken - # @param [Domain] domain The domain the (workflow or activity types + # @param [Domain] domain The domain the (workflow or activity types # belong to. def initialize domain, options = {} @@ -29,7 +29,7 @@ def initialize domain, options = {} @named = options[:named] - @registration_status = options[:registration_status] ? + @registration_status = options[:registration_status] ? options[:registration_status].to_s.upcase : 'REGISTERED' @reverse_order = options.key?(:reverse_order) ? @@ -81,7 +81,7 @@ def reverse_order collection_with(:reverse_order => true) end - # @return [TypeCollection] Returns a collection that + # @return [TypeCollection] Returns a collection that # enumerates types with the given name. Each instance # will have a different version. def named name @@ -112,7 +112,7 @@ def _each_item next_token, limit, options = {}, &block options[:maximum_page_size] = limit if limit options[:registration_status] ||= @registration_status options[:name] ||= @named if @named # may be nil - options[:reverse_order] = @reverse_order unless + options[:reverse_order] = @reverse_order unless options.has_key?(:reverse_order) @@ -123,12 +123,12 @@ def _each_item next_token, limit, options = {}, &block response = client.send(client_method, options) response.data['typeInfos'].each do |desc| - type = member_class.new_from(client_method, desc, domain, - desc[type_key]['name'], - desc[type_key]['version']) + type = member_class.new_from(client_method, desc, domain, + desc[type_key]['name'], + desc[type_key]['version']) yield(type) - + end response.data['nextPageToken'] diff --git a/lib/aws/simple_workflow/workflow_execution.rb b/lib/aws/simple_workflow/workflow_execution.rb index 4cc7774ff99..916cd3ffe96 100644 --- a/lib/aws/simple_workflow/workflow_execution.rb +++ b/lib/aws/simple_workflow/workflow_execution.rb @@ -14,18 +14,18 @@ module AWS class SimpleWorkflow - # @attr_reader [Symbol] child_policy The policy to use for the child - # workflow executions if this workflow execution is terminated. + # @attr_reader [Symbol] child_policy The policy to use for the child + # workflow executions if this workflow execution is terminated. # The return value will be one of the following values: # # * +:terminate+ - the child executions will be terminated. # # * +:request_cancel+ - a request to cancel will be attempted for each - # child execution by recording a WorkflowExecutionCancelRequested + # child execution by recording a WorkflowExecutionCancelRequested # event in its history. It is up to the decider to take appropriate # actions when it receives an execution history with this event. # - # * +:abandon+ - no action will be taken. The child executions will + # * +:abandon+ - no action will be taken. The child executions will # continue to run. # # @attr_reader [String] start_to_close_timeout The total allowed @@ -34,7 +34,7 @@ class SimpleWorkflow # The return value will be formatted as an ISO 8601 duration (e.g. # 'PnYnMnDTnHnMnS'). # - # @attr_reader [String] task_list The task list used for the decision + # @attr_reader [String] task_list The task list used for the decision # tasks generated for this workflow execution. # # @attr_reader [String] task_start_to_close_timeout The maximum duration @@ -47,24 +47,24 @@ class SimpleWorkflow # was closed. Returns nil if this execution is not closed. # # @attr_reader [Time] started_at The time when the execution was started. - # - # @attr_reader [Time,nil] latest_activity_task_scheduled_at The time + # + # @attr_reader [Time,nil] latest_activity_task_scheduled_at The time # when the last activity task was scheduled for this workflow execution. - # You can use this information to determine if the workflow has not - # made progress for an unusually long period of time and might + # You can use this information to determine if the workflow has not + # made progress for an unusually long period of time and might # require a corrective action. # # @attr_reader [String,nil] latest_execution_context The latest execution - # context provided by the decider for this workflow execution. A decider - # can provide an execution context, which is a free form string, when + # context provided by the decider for this workflow execution. A decider + # can provide an execution context, which is a free form string, when # closing a decision task. # # @attr_reader [Hash] open_counts Returns a hash of counts, including: # +:open_timers+, +:open_child_workflow_executions+, +:open_decision_tasks+, # and +:open_activity_tasks+. - # + # class WorkflowExecution < Resource - + def initialize domain, workflow_id, run_id, options = {} @domain = domain @workflow_id = workflow_id @@ -104,8 +104,8 @@ def initialize domain, workflow_id, run_id, options = {} info_attribute :parent_details, :from => 'parent', :static => true protected :parent_details - info_attribute :started_at, - :from => 'startTimestamp', + info_attribute :started_at, + :from => 'startTimestamp', :timestamp => true, :static => true @@ -115,7 +115,7 @@ def initialize domain, workflow_id, run_id, options = {} info_attribute :type_details, :from => 'workflowType', :static => true protected :type_details - attribute :latest_activity_task_scheduled_at, + attribute :latest_activity_task_scheduled_at, :from => 'latestActivityTaskTimestamp', :timestamp => true @@ -131,13 +131,13 @@ def initialize domain, workflow_id, run_id, options = {} # list_workflow_executions provides ONLY type attributes provider( - :list_open_workflow_executions, + :list_open_workflow_executions, :list_closed_workflow_executions ) do |provider| provider.provides *info_attributes.keys provider.find do |resp| execution = { 'workflowId' => workflow_id, 'runId' => run_id } - resp.data['executionInfos'].find do |desc| + resp.data['executionInfos'].find do |desc| desc['execution'] == execution end end @@ -162,18 +162,18 @@ def initialize domain, workflow_id, run_id, options = {} # # * +:open+ - The execution is still running. # * +:completed+ - The execution was successfully completed. - # * +:canceled+ - The execution was canceled, cancellation allows - # the implementation to gracefully clean up before the execution + # * +:canceled+ - The execution was canceled, cancellation allows + # the implementation to gracefully clean up before the execution # is closed. # * +:failed+ - The execution failed to complete. # and was automatically timed out. # * +:continued_as_new+ - The execution is logically continued. This - # means the current execution was completed and a new execution + # means the current execution was completed and a new execution # was started to carry on the workflow. # * +:terminated+ - The execution was force terminated. # * +:timed_out+ - The execution did not complete in the allotted # time and was automatically timed out. - # + # def status AWS.memoize do execution_status == :open ? :open : (close_status || :closed) @@ -246,18 +246,18 @@ def parent end end - # Records a WorkflowExecutionSignaled event in the workflow execution + # Records a WorkflowExecutionSignaled event in the workflow execution # history and creates a decision task for the workflow execution. # # workflow_execution.signal('signal_name', :input => '...') # - # @param [String] signal_name The name of the signal. This name must be + # @param [String] signal_name The name of the signal. This name must be # meaningful to the target workflow. # # @param [Hash] options # - # @option options [String] :input (nil) Data to attach to the - # WorkflowExecutionSignaled event in the target workflow + # @option options [String] :input (nil) Data to attach to the + # WorkflowExecutionSignaled event in the target workflow # execution's history. # # @return [nil] @@ -267,13 +267,13 @@ def signal signal_name, options = {} domain.workflow_executions.signal(workflow_id, signal_name, options) end - # Records a WorkflowExecutionCancelRequested event in the currently + # Records a WorkflowExecutionCancelRequested event in the currently # running workflow execution. This logically requests the cancellation - # of the workflow execution as a whole. It is up to the decider to - # take appropriate actions when it receives an execution history + # of the workflow execution as a whole. It is up to the decider to + # take appropriate actions when it receives an execution history # with this event. # - # @note Because this action allows the workflow to properly clean up + # @note Because this action allows the workflow to properly clean up # and gracefully close, it should be used instead of {#terminate} # when possible. # @@ -284,41 +284,41 @@ def request_cancel domain.workflow_executions.request_cancel(workflow_id, options) end - # Records a WorkflowExecutionTerminated event and forces closure of - # the workflow execution. The child policy, registered with the - # workflow type or specified when starting this execution, is applied + # Records a WorkflowExecutionTerminated event and forces closure of + # the workflow execution. The child policy, registered with the + # workflow type or specified when starting this execution, is applied # to any open child workflow executions of this workflow execution. # - # @note If the workflow execution was in progress, it is terminated + # @note If the workflow execution was in progress, it is terminated # immediately. # - # @note You should consider canceling the workflow execution + # @note You should consider canceling the workflow execution # instead because it allows the workflow to gracefully close # while terminate does not. # # @param [Hash] options # # @option options [Symbol] :child_policy (nil) - # If set, specifies the policy to use for the child workflow - # executions of the workflow execution being terminated. This + # If set, specifies the policy to use for the child workflow + # executions of the workflow execution being terminated. This # policy overrides the default child policy. Valid policies include: # # * +:terminate+ - the child executions will be terminated. # # * +:request_cancel+ - a request to cancel will be attempted for each - # child execution by recording a WorkflowExecutionCancelRequested + # child execution by recording a WorkflowExecutionCancelRequested # event in its history. It is up to the decider to take appropriate # actions when it receives an execution history with this event. # - # * +:abandon+ - no action will be taken. The child executions will + # * +:abandon+ - no action will be taken. The child executions will # continue to run. # - # @option options [String] :details (nil) Optional details for + # @option options [String] :details (nil) Optional details for # terminating the workflow execution. # - # @option options [String] :reason (nil) An optional descriptive + # @option options [String] :reason (nil) An optional descriptive # reason for terminating the workflow execution. - # + # # @return [nil] # def terminate options = {} @@ -330,12 +330,12 @@ def terminate options = {} # # @note See {WorkflowExecutionCollection#count} for a broader count. # - # @note This operation is eventually consistent. The results are best + # @note This operation is eventually consistent. The results are best # effort and may not exactly reflect recent updates and changes. # # @param [Hash] options # - # @option options [Symbol] :status (:open) Specifies that + # @option options [Symbol] :status (:open) Specifies that # status of the workflow executions to count. Defaults to # open workflows. # @@ -343,18 +343,18 @@ def terminate options = {} # * +:closed+ # # @option options [Array