azure 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +3 -0
- data/Gemfile.lock +36 -0
- data/README.md +3 -0
- data/Rakefile +81 -0
- data/azure.gemspec +20 -9
- data/lib/azure.rb +4 -0
- data/lib/azure/atom.rb +170 -0
- data/lib/azure/auth.rb +29 -0
- data/lib/azure/blobs.rb +620 -0
- data/lib/azure/blobs/blob.rb +360 -0
- data/lib/azure/blobs/container.rb +209 -0
- data/lib/azure/blobs/service.rb +396 -0
- data/lib/azure/blobs/shared_access_signature.rb +84 -0
- data/lib/azure/blobs/uri.rb +60 -0
- data/lib/azure/configuration.rb +121 -0
- data/lib/azure/core/auth/shared_key.rb +95 -0
- data/lib/azure/core/auth/shared_key_lite.rb +34 -0
- data/lib/azure/core/collection.rb +118 -0
- data/lib/azure/core/service.rb +43 -0
- data/lib/azure/core/signer.rb +32 -0
- data/lib/azure/core/utils/interval.rb +97 -0
- data/lib/azure/core/utils/queryable.rb +74 -0
- data/lib/azure/core/utils/storage_service_properties.rb +83 -0
- data/lib/azure/core/utils/string.rb +59 -0
- data/lib/azure/error.rb +72 -0
- data/lib/azure/queues.rb +272 -0
- data/lib/azure/queues/message.rb +174 -0
- data/lib/azure/queues/queue.rb +187 -0
- data/lib/azure/queues/service.rb +263 -0
- data/lib/azure/queues/service_properties.rb +152 -0
- data/lib/azure/queues/uri.rb +78 -0
- data/lib/azure/request.rb +102 -0
- data/lib/azure/response.rb +93 -0
- data/lib/azure/service_bus.rb +4 -0
- data/lib/azure/service_bus/auth/authorizer.rb +22 -0
- data/lib/azure/service_bus/auth/uri.rb +52 -0
- data/lib/azure/service_bus/auth/wrap.rb +37 -0
- data/lib/azure/service_bus/auth/wrap_service.rb +76 -0
- data/lib/azure/service_bus/auth/wrap_token.rb +45 -0
- data/lib/azure/service_bus/auth/wrap_token_manager.rb +46 -0
- data/lib/azure/service_bus/brokered_message.rb +139 -0
- data/lib/azure/service_bus/brokered_message_serializer.rb +113 -0
- data/lib/azure/service_bus/queues.rb +194 -0
- data/lib/azure/service_bus/queues/queue.rb +100 -0
- data/lib/azure/service_bus/queues/queue_serializer.rb +51 -0
- data/lib/azure/service_bus/queues/service.rb +154 -0
- data/lib/azure/service_bus/queues/uri.rb +80 -0
- data/lib/azure/service_bus/rules.rb +110 -0
- data/lib/azure/service_bus/rules/rule.rb +97 -0
- data/lib/azure/service_bus/rules/service.rb +122 -0
- data/lib/azure/service_bus/rules/uri.rb +39 -0
- data/lib/azure/service_bus/service_bus_service.rb +22 -0
- data/lib/azure/service_bus/subscriptions.rb +170 -0
- data/lib/azure/service_bus/subscriptions/service.rb +133 -0
- data/lib/azure/service_bus/subscriptions/subscription.rb +164 -0
- data/lib/azure/service_bus/subscriptions/subscription_serializer.rb +74 -0
- data/lib/azure/service_bus/subscriptions/uri.rb +71 -0
- data/lib/azure/service_bus/topics.rb +120 -0
- data/lib/azure/service_bus/topics/service.rb +98 -0
- data/lib/azure/service_bus/topics/topic.rb +122 -0
- data/lib/azure/service_bus/topics/topic_serializer.rb +44 -0
- data/lib/azure/service_bus/topics/uri.rb +58 -0
- data/lib/azure/service_runtime/client/goal_state_pipe_monitor.rb +21 -0
- data/lib/azure/service_runtime/client/goal_state_protocol.rb +18 -0
- data/lib/azure/service_runtime/client/runtime_client.rb +135 -0
- data/lib/azure/service_runtime/deployment.rb +24 -0
- data/lib/azure/service_runtime/local_resource.rb +15 -0
- data/lib/azure/service_runtime/role.rb +17 -0
- data/lib/azure/service_runtime/role_environment.rb +206 -0
- data/lib/azure/service_runtime/role_environment_change.rb +32 -0
- data/lib/azure/service_runtime/role_instance.rb +35 -0
- data/lib/azure/service_runtime/role_instance_endpoint.rb +14 -0
- data/lib/azure/tables.rb +215 -0
- data/lib/azure/tables/auth/shared_key.rb +71 -0
- data/lib/azure/tables/auth/shared_key_lite.rb +30 -0
- data/lib/azure/tables/entities_collection.rb +66 -0
- data/lib/azure/tables/entity.rb +127 -0
- data/lib/azure/tables/service.rb +211 -0
- data/lib/azure/tables/table.rb +129 -0
- data/lib/azure/tables/tables_collection.rb +62 -0
- data/lib/azure/tables/types.rb +65 -0
- data/lib/azure/tables/uri.rb +62 -0
- data/test/fixtures/32px-fulls-black.jpg +0 -0
- data/test/fixtures/all_containers.xml +20 -0
- data/test/fixtures/all_tables.xml +22 -0
- data/test/fixtures/create_table_response_entry.xml +15 -0
- data/test/fixtures/error.xml +5 -0
- data/test/fixtures/insert_entity_response_entry.xml +25 -0
- data/test/fixtures/messages.xml +12 -0
- data/test/fixtures/query_entities_empty_response.xml +7 -0
- data/test/fixtures/query_entities_response.xml +45 -0
- data/test/fixtures/queue_service_properties.xml +22 -0
- data/test/fixtures/queue_service_properties_original.xml +19 -0
- data/test/fixtures/queues.xml +16 -0
- data/test/fixtures/sb_default_create_queue_response.xml +23 -0
- data/test/fixtures/sb_default_create_topic_response.xml +18 -0
- data/test/fixtures/sb_get_access_token_response.txt +1 -0
- data/test/fixtures/sb_queues_runtime_peek_message_response_headers.txt +9 -0
- data/test/integration/blobs/auth_test.rb +19 -0
- data/test/integration/blobs/blob_test.rb +61 -0
- data/test/integration/blobs/clear_page_range_test.rb +19 -0
- data/test/integration/blobs/copy_test.rb +33 -0
- data/test/integration/blobs/create_blobs_test.rb +51 -0
- data/test/integration/blobs/create_container_test.rb +13 -0
- data/test/integration/blobs/create_snapshot_test.rb +17 -0
- data/test/integration/blobs/delete_blob_snapshots_test.rb +19 -0
- data/test/integration/blobs/delete_blobs_test.rb +25 -0
- data/test/integration/blobs/delete_container_test.rb +24 -0
- data/test/integration/blobs/delete_snapshot_test.rb +17 -0
- data/test/integration/blobs/get_blob_snapshot_test.rb +18 -0
- data/test/integration/blobs/get_blobs_test.rb +31 -0
- data/test/integration/blobs/get_page_range_test.rb +19 -0
- data/test/integration/blobs/list_blobs_test.rb +39 -0
- data/test/integration/blobs/list_containers_test.rb +28 -0
- data/test/integration/blobs/manage_blob_leases_test.rb +45 -0
- data/test/integration/blobs/manage_blob_metadata_test.rb +51 -0
- data/test/integration/blobs/manage_blob_properties_test.rb +25 -0
- data/test/integration/blobs/manage_blob_service_properties_test.rb +38 -0
- data/test/integration/blobs/manage_container_metadata_test.rb +46 -0
- data/test/integration/blobs/manage_container_permissions_test.rb +17 -0
- data/test/integration/blobs/update_page_range_test.rb +20 -0
- data/test/integration/queues/clear_messages_test.rb +22 -0
- data/test/integration/queues/create_queue_test.rb +13 -0
- data/test/integration/queues/delete_message_test.rb +42 -0
- data/test/integration/queues/delete_queue_test.rb +24 -0
- data/test/integration/queues/get_messages_test.rb +39 -0
- data/test/integration/queues/list_queues_test.rb +43 -0
- data/test/integration/queues/manage_queue_metadata_test.rb +45 -0
- data/test/integration/queues/manage_queue_service_properties_test.rb +27 -0
- data/test/integration/queues/peek_messages_test.rb +55 -0
- data/test/integration/queues/put_message_test.rb +31 -0
- data/test/integration/queues/update_message_test.rb +46 -0
- data/test/integration/service_bus/auth_test.rb +18 -0
- data/test/integration/service_bus/queues/create_queue_test.rb +25 -0
- data/test/integration/service_bus/queues/delete_message_from_queue_test.rb +29 -0
- data/test/integration/service_bus/queues/delete_queue_test.rb +25 -0
- data/test/integration/service_bus/queues/get_queue_test.rb +23 -0
- data/test/integration/service_bus/queues/list_queues_test.rb +39 -0
- data/test/integration/service_bus/queues/peek_message_from_queue_test.rb +34 -0
- data/test/integration/service_bus/queues/read_and_delete_message_from_queue_test.rb +31 -0
- data/test/integration/service_bus/queues/send_message_to_queue_test.rb +22 -0
- data/test/integration/service_bus/queues/unlock_message_from_queue_test.rb +36 -0
- data/test/integration/service_bus/rules/create_rule_test.rb +19 -0
- data/test/integration/service_bus/rules/delete_rule_test.rb +17 -0
- data/test/integration/service_bus/rules/get_rule_test.rb +21 -0
- data/test/integration/service_bus/rules/list_rules_test.rb +24 -0
- data/test/integration/service_bus/rules/rule_test.rb +16 -0
- data/test/integration/service_bus/subscriptions/create_subscription_test.rb +25 -0
- data/test/integration/service_bus/subscriptions/delete_message_from_subscription_test.rb +31 -0
- data/test/integration/service_bus/subscriptions/delete_subscription_test.rb +30 -0
- data/test/integration/service_bus/subscriptions/fetch_subscription_test.rb +28 -0
- data/test/integration/service_bus/subscriptions/list_subscriptions_test.rb +23 -0
- data/test/integration/service_bus/subscriptions/peek_lock_message_from_subscription_test.rb +42 -0
- data/test/integration/service_bus/subscriptions/read_delete_message_from_subscription_test.rb +36 -0
- data/test/integration/service_bus/subscriptions/subscription_test.rb +31 -0
- data/test/integration/service_bus/subscriptions/unlock_message_from_subscription_test.rb +43 -0
- data/test/integration/service_bus/topics/create_topic_test.rb +25 -0
- data/test/integration/service_bus/topics/delete_topic_test.rb +25 -0
- data/test/integration/service_bus/topics/get_topic_test.rb +23 -0
- data/test/integration/service_bus/topics/list_topics_test.rb +39 -0
- data/test/integration/service_bus/topics/send_message_to_topic_test.rb +23 -0
- data/test/integration/tables/auth_test.rb +29 -0
- data/test/integration/tables/creating_tables_test.rb +16 -0
- data/test/integration/tables/delete_entity_test.rb +39 -0
- data/test/integration/tables/deleting_table_test.rb +22 -0
- data/test/integration/tables/insert_entity_test.rb +23 -0
- data/test/integration/tables/merge_entity_test.rb +28 -0
- data/test/integration/tables/query_entities_test.rb +131 -0
- data/test/integration/tables/query_tables_test.rb +63 -0
- data/test/integration/tables/update_entity_test.rb +54 -0
- data/test/integration/test_helper.rb +14 -0
- data/test/support/blobs.rb +12 -0
- data/test/support/env.rb +5 -0
- data/test/support/fixtures.rb +22 -0
- data/test/support/stubs.rb +28 -0
- data/test/support/table_names.rb +44 -0
- data/test/test_helper.rb +10 -0
- data/test/unit/atom_test.rb +58 -0
- data/test/unit/auth_test.rb +24 -0
- data/test/unit/blobs/blob_test.rb +5 -0
- data/test/unit/blobs/container_test.rb +67 -0
- data/test/unit/blobs/service_test.rb +17 -0
- data/test/unit/blobs/shared_access_signature_test.rb +66 -0
- data/test/unit/blobs_test.rb +156 -0
- data/test/unit/core/service_test.rb +57 -0
- data/test/unit/core/utils/interval_test.rb +70 -0
- data/test/unit/core/utils/queryable_test.rb +69 -0
- data/test/unit/core/utils/storage_service_properties_test.rb +66 -0
- data/test/unit/error_test.rb +39 -0
- data/test/unit/queues/message_test.rb +40 -0
- data/test/unit/queues/queue_test.rb +64 -0
- data/test/unit/queues/service_properties.rb +35 -0
- data/test/unit/request_test.rb +38 -0
- data/test/unit/response_test.rb +43 -0
- data/test/unit/service_bus/auth/authorizer_test.rb +27 -0
- data/test/unit/service_bus/auth/wrap_token_test.rb +28 -0
- data/test/unit/service_bus/queues/queue_test.rb +38 -0
- data/test/unit/service_bus/topics/topic_test.rb +33 -0
- data/test/unit/service_runtime/data/goalstate.xml +9 -0
- data/test/unit/service_runtime/data/roleenvironmentdata.xml +29 -0
- data/test/unit/service_runtime/data/runtime.xml +6 -0
- data/test/unit/service_runtime/role_environment_test.rb +144 -0
- data/test/unit/tables/auth/shared_key_lite_test.rb +39 -0
- data/test/unit/tables/auth/shared_key_test.rb +45 -0
- data/test/unit/tables/entities_collection_test.rb +39 -0
- data/test/unit/tables/entity_test.rb +72 -0
- data/test/unit/tables/table_test.rb +57 -0
- data/test/unit/tables_test.rb +302 -0
- data/test/unit/types_test.rb +67 -0
- metadata +310 -47
- data/LICENSE +0 -0
- data/README +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require "openssl"
|
|
2
|
+
require "base64"
|
|
3
|
+
require "azure/configuration"
|
|
4
|
+
|
|
5
|
+
module Azure
|
|
6
|
+
module Core
|
|
7
|
+
# Public: Utility class to sign strings with HMAC-256 and then encode the
|
|
8
|
+
# signed string using Base64.
|
|
9
|
+
class Signer
|
|
10
|
+
# The Azure account's access key.
|
|
11
|
+
attr :access_key
|
|
12
|
+
|
|
13
|
+
# Public: Initialize the Signer.
|
|
14
|
+
#
|
|
15
|
+
# access_key - The Azure access_key encoded in Base64. Defaults to the one
|
|
16
|
+
# in the global configuration.
|
|
17
|
+
def initialize(access_key=Azure.config.access_key)
|
|
18
|
+
@access_key = Base64.strict_decode64(access_key)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Public: Generate an HMAC signature.
|
|
22
|
+
#
|
|
23
|
+
# body - The string to sign.
|
|
24
|
+
#
|
|
25
|
+
# Returns a Base64 String signed with HMAC.
|
|
26
|
+
def sign(body)
|
|
27
|
+
signed = OpenSSL::HMAC.digest("sha256", access_key, body)
|
|
28
|
+
Base64.strict_encode64(signed)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require "delegate"
|
|
2
|
+
|
|
3
|
+
module Azure
|
|
4
|
+
module Core
|
|
5
|
+
module Utils
|
|
6
|
+
# Public: Helper class to decorate a numeric duration so it can be output
|
|
7
|
+
# as an ISO8601-compliant Duration. (This class only implements a subset
|
|
8
|
+
# of the ISO8601 standard, since it's what Microsoft appears to be using.)
|
|
9
|
+
#
|
|
10
|
+
# Examples
|
|
11
|
+
#
|
|
12
|
+
# # Initialize an Interval from a Number, or use .try_convert to be
|
|
13
|
+
# # intelligent:
|
|
14
|
+
#
|
|
15
|
+
# Interval.new(10) #=> PT10S
|
|
16
|
+
# Interval.try_convert(10) #=> PT10S
|
|
17
|
+
# Interval.try_convert("PT10S") #=> PT10S
|
|
18
|
+
# Interval.try_convert(nil) #=> nil
|
|
19
|
+
class Interval < SimpleDelegator
|
|
20
|
+
|
|
21
|
+
# Public: Attempt to convert an object into an Interval.
|
|
22
|
+
#
|
|
23
|
+
# object - An object that might be converted into an Interval.
|
|
24
|
+
#
|
|
25
|
+
# Returns an Interval or nil.
|
|
26
|
+
def self.try_convert(object)
|
|
27
|
+
if object.respond_to?(:to_interval)
|
|
28
|
+
object.to_interval
|
|
29
|
+
elsif object.respond_to?(:to_int)
|
|
30
|
+
new(object)
|
|
31
|
+
elsif object.respond_to?(:to_str)
|
|
32
|
+
parse(object)
|
|
33
|
+
else
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Public: Parse a String into an Interval.
|
|
39
|
+
#
|
|
40
|
+
# string - A String in the Interval format.
|
|
41
|
+
#
|
|
42
|
+
# Returns an Interval.
|
|
43
|
+
def self.parse(string)
|
|
44
|
+
re = /
|
|
45
|
+
P (?<d>[\d]+D)? # match days
|
|
46
|
+
(?:
|
|
47
|
+
T (?<h>[\d]+H)? # match hours
|
|
48
|
+
(?<m>[\d]+M)? # match minutes
|
|
49
|
+
(?<s>[\d\.]+S)? # match seconds
|
|
50
|
+
)?
|
|
51
|
+
/x
|
|
52
|
+
|
|
53
|
+
match = re.match(string)
|
|
54
|
+
|
|
55
|
+
return nil if match.nil?
|
|
56
|
+
|
|
57
|
+
days = match[:d].to_i
|
|
58
|
+
hours = match[:h].to_i
|
|
59
|
+
minutes = match[:m].to_i
|
|
60
|
+
seconds = match[:s].to_f
|
|
61
|
+
|
|
62
|
+
new(seconds + minutes * 60 + hours * 3600 + days * 86400)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Public: Return this amount of seconds formatted as an interval.
|
|
66
|
+
#
|
|
67
|
+
# Returns a String.
|
|
68
|
+
def to_s
|
|
69
|
+
days = to_i / 86400
|
|
70
|
+
hours = (to_i % 86400) / 3600
|
|
71
|
+
minutes = (to_i % 3600) / 60
|
|
72
|
+
seconds = (self % 60)
|
|
73
|
+
|
|
74
|
+
days = "%<d>s" % {
|
|
75
|
+
d: days.zero? ? nil : "#{days}D"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
time = "%<h>s%<m>s%<s>s" % {
|
|
79
|
+
h: hours.zero? ? nil : "#{hours}H",
|
|
80
|
+
m: minutes.zero? ? nil : "#{minutes}M",
|
|
81
|
+
s: nonzero? && seconds.zero? ? nil : "#{seconds}S"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
"P#{days}" + (time.empty? ? "" : "T#{time}")
|
|
85
|
+
end
|
|
86
|
+
alias_method :inspect, :to_s
|
|
87
|
+
|
|
88
|
+
# Public: Convert this object into an interval.
|
|
89
|
+
#
|
|
90
|
+
# Returns self.
|
|
91
|
+
def to_interval
|
|
92
|
+
self
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
|
|
3
|
+
module Azure
|
|
4
|
+
module Core
|
|
5
|
+
module Utils
|
|
6
|
+
# Convenience module to build query strings.
|
|
7
|
+
module Queryable
|
|
8
|
+
# Public: Build a query from a options hash.
|
|
9
|
+
#
|
|
10
|
+
# options - A hash of options.
|
|
11
|
+
# :partition_key - Only get entities with this PartitionKey
|
|
12
|
+
# (optional, but :row_key must also be
|
|
13
|
+
# included).
|
|
14
|
+
# :row_key - Only get entities with this RowKey
|
|
15
|
+
# (optional, but :partition_key must also
|
|
16
|
+
# be included).
|
|
17
|
+
# :select - An array with property names. Only these
|
|
18
|
+
# properties are provided from the
|
|
19
|
+
# returned returned set of entities.
|
|
20
|
+
# :filter - A string to filter results.
|
|
21
|
+
# :top - Returns only the top n results from the set.
|
|
22
|
+
# :skip - Skips the first n results from the set.
|
|
23
|
+
#
|
|
24
|
+
# Returns a query string
|
|
25
|
+
def build_query(options)
|
|
26
|
+
select = options.fetch(:select, nil)
|
|
27
|
+
select = "$select=#{select.join(",")}" if select
|
|
28
|
+
|
|
29
|
+
filter = options.fetch(:filter, nil)
|
|
30
|
+
filter = "$filter=#{filter}" if filter
|
|
31
|
+
|
|
32
|
+
top = options.fetch(:top, nil)
|
|
33
|
+
top = "$top=#{top}" if top
|
|
34
|
+
|
|
35
|
+
skip = options.fetch(:skip, nil)
|
|
36
|
+
skip = "$skip=#{skip}" if skip
|
|
37
|
+
|
|
38
|
+
next_partition_key = options.fetch("NextPartitionKey", nil)
|
|
39
|
+
next_partition_key = "NextPartitionKey=#{next_partition_key}" if next_partition_key
|
|
40
|
+
|
|
41
|
+
next_row_key = options.fetch("NextRowKey", nil)
|
|
42
|
+
next_row_key = "NextRowKey=#{next_row_key}" if next_row_key
|
|
43
|
+
|
|
44
|
+
next_table_name = options.fetch("NextTableName", nil)
|
|
45
|
+
next_table_name = "NextTableName=#{next_table_name}" if next_table_name
|
|
46
|
+
|
|
47
|
+
query = [next_table_name, next_partition_key, next_row_key,
|
|
48
|
+
select, filter, top, skip].compact.join("&")
|
|
49
|
+
|
|
50
|
+
query != "" ? ::URI.encode(query) : nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Public: Build a Hash with translated keys from a options hash.
|
|
54
|
+
#
|
|
55
|
+
# options - A hash of options.
|
|
56
|
+
# :top - Returns only the top n results from the set.
|
|
57
|
+
# :skip - Skips the first n results from the set.
|
|
58
|
+
#
|
|
59
|
+
# Returns a query string
|
|
60
|
+
def translate_options_hash(options)
|
|
61
|
+
hash = {}
|
|
62
|
+
|
|
63
|
+
skip = options.fetch(:skip, nil)
|
|
64
|
+
hash["$skip"] = skip if skip
|
|
65
|
+
|
|
66
|
+
top = options.fetch(:top, nil)
|
|
67
|
+
hash["$top"] = top if top
|
|
68
|
+
|
|
69
|
+
hash
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
|
|
3
|
+
module Azure
|
|
4
|
+
module Core
|
|
5
|
+
module Utils
|
|
6
|
+
class StorageServiceProperties
|
|
7
|
+
|
|
8
|
+
REQUIRED_ELEMENTS = {
|
|
9
|
+
"Version" => true,
|
|
10
|
+
"Delete" => true,
|
|
11
|
+
"Read" => true,
|
|
12
|
+
"Write" => true,
|
|
13
|
+
"Enabled" => true,
|
|
14
|
+
"RetentionPolicy / Enabled" => true,
|
|
15
|
+
"RetentionPolicy / Days" => true,
|
|
16
|
+
"IncludeAPIs" => "Metrics"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def self.hash_to_xml(hash)
|
|
20
|
+
doc = Nokogiri::XML::Document.new
|
|
21
|
+
doc.encoding = "utf-8"
|
|
22
|
+
root = Nokogiri::XML::Node.new("StorageServiceProperties", doc)
|
|
23
|
+
iterate_hash(hash, root, doc)
|
|
24
|
+
doc << root
|
|
25
|
+
validate(doc)
|
|
26
|
+
doc
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.hash_to_xml_string(hash)
|
|
30
|
+
hash_to_xml(hash).to_xml
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.xml_string_to_hash(xml)
|
|
34
|
+
doc = Nokogiri::XML.parse(xml)
|
|
35
|
+
iterate_node(doc)["StorageServiceProperties"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def self.iterate_node(node)
|
|
41
|
+
node.children.each_with_object({}) do |node, hash|
|
|
42
|
+
|
|
43
|
+
next if !node.element?
|
|
44
|
+
|
|
45
|
+
if node.children.size == 1
|
|
46
|
+
hash[node.name] = node.text
|
|
47
|
+
else
|
|
48
|
+
hash[node.name] = iterate_node(node)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.iterate_hash(hash, root, doc)
|
|
54
|
+
hash.each do |key, value|
|
|
55
|
+
if value.is_a?(Hash)
|
|
56
|
+
node = Nokogiri::XML::Node.new(key, doc)
|
|
57
|
+
iterate_hash(value, node, doc)
|
|
58
|
+
root << node
|
|
59
|
+
else
|
|
60
|
+
node = Nokogiri::XML::Node.new(key, doc)
|
|
61
|
+
node.content = value
|
|
62
|
+
root << node
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.validate(doc)
|
|
68
|
+
missing_keys = []
|
|
69
|
+
|
|
70
|
+
REQUIRED_ELEMENTS.each do |key,value|
|
|
71
|
+
if value == true
|
|
72
|
+
missing_keys << key if (doc / key).empty?
|
|
73
|
+
elsif (doc / value).empty? || (doc / key).empty?
|
|
74
|
+
missing_keys << key
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
raise StandardError, "StorageServicePropertiesMissing", missing_keys if !missing_keys.empty?
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Azure
|
|
2
|
+
module Core
|
|
3
|
+
module Utils
|
|
4
|
+
# Convenience module to build query strings.
|
|
5
|
+
module String
|
|
6
|
+
# The following methods have been borrowed (stolen?) from ActiveSupport
|
|
7
|
+
|
|
8
|
+
# By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
|
|
9
|
+
# is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase.
|
|
10
|
+
#
|
|
11
|
+
# +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
|
|
12
|
+
#
|
|
13
|
+
# Examples:
|
|
14
|
+
# "active_model".camelize # => "ActiveModel"
|
|
15
|
+
# "active_model".camelize(:lower) # => "activeModel"
|
|
16
|
+
# "active_model/errors".camelize # => "ActiveModel::Errors"
|
|
17
|
+
# "active_model/errors".camelize(:lower) # => "activeModel::Errors"
|
|
18
|
+
#
|
|
19
|
+
# As a rule of thumb you can think of +camelize+ as the inverse of +underscore+,
|
|
20
|
+
# though there are cases where that does not hold:
|
|
21
|
+
#
|
|
22
|
+
# "SSLError".underscore.camelize # => "SslError"
|
|
23
|
+
def camelize(term, uppercase_first_letter = true)
|
|
24
|
+
string = term.to_s
|
|
25
|
+
acronyms = {}
|
|
26
|
+
if uppercase_first_letter
|
|
27
|
+
string = string.sub(/^[a-z\d]*/) { acronyms[$&] || $&.capitalize }
|
|
28
|
+
else
|
|
29
|
+
string = string.sub(/^(?:(?=a)b(?=\b|[A-Z_])|\w)/) { $&.downcase }
|
|
30
|
+
end
|
|
31
|
+
string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{acronyms[$2] || $2.capitalize}" }.gsub('/', '::')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Makes an underscored, lowercase form from the expression in the string.
|
|
35
|
+
#
|
|
36
|
+
# Changes '::' to '/' to convert namespaces to paths.
|
|
37
|
+
#
|
|
38
|
+
# Examples:
|
|
39
|
+
# "ActiveModel".underscore # => "active_model"
|
|
40
|
+
# "ActiveModel::Errors".underscore # => "active_model/errors"
|
|
41
|
+
#
|
|
42
|
+
# As a rule of thumb you can think of +underscore+ as the inverse of +camelize+,
|
|
43
|
+
# though there are cases where that does not hold:
|
|
44
|
+
#
|
|
45
|
+
# "SSLError".underscore.camelize # => "SslError"
|
|
46
|
+
def underscore(camel_cased_word)
|
|
47
|
+
word = camel_cased_word.to_s.dup
|
|
48
|
+
word.gsub!(/::/, '/')
|
|
49
|
+
word.gsub!(/(?:([A-Za-z\d])|^)((?=a)b)(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
|
|
50
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
|
51
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
|
52
|
+
word.tr!("-", "_")
|
|
53
|
+
word.downcase!
|
|
54
|
+
word
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/azure/error.rb
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require "xml"
|
|
2
|
+
|
|
3
|
+
module Azure
|
|
4
|
+
# Public: Superclass for errors generated from this library, so people can
|
|
5
|
+
# just rescue this for generic error handling.
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
# Public: FileUploadError
|
|
9
|
+
class FileUploadError < ::Azure::Error; end
|
|
10
|
+
|
|
11
|
+
# Public: Class for handling all HTTP response errors.
|
|
12
|
+
class HTTPError < ::Azure::Error
|
|
13
|
+
# Public: The HTTP status code of this error.
|
|
14
|
+
#
|
|
15
|
+
# Returns a Fixnum.
|
|
16
|
+
attr :code
|
|
17
|
+
|
|
18
|
+
# Public: The type of error, as described by the Azure documentation.
|
|
19
|
+
#
|
|
20
|
+
# Returns a String.
|
|
21
|
+
attr :type
|
|
22
|
+
|
|
23
|
+
# Public: The English description of this error.
|
|
24
|
+
#
|
|
25
|
+
# Returns a String.
|
|
26
|
+
attr :description
|
|
27
|
+
|
|
28
|
+
# Public: Initialize an error.
|
|
29
|
+
#
|
|
30
|
+
# http_response - An Azure::Response.
|
|
31
|
+
def initialize(http_response)
|
|
32
|
+
@http_response = http_response
|
|
33
|
+
@code = http_response.code
|
|
34
|
+
parse_response
|
|
35
|
+
super("#{type} (#{code}): #{description}")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Extract the relevant information from the response's body. If the response
|
|
39
|
+
# body is not an XML, we return an 'Unknown' error with the entire body as
|
|
40
|
+
# the description.
|
|
41
|
+
#
|
|
42
|
+
# Returns nothing.
|
|
43
|
+
def parse_response
|
|
44
|
+
if @http_response.body.include?("<")
|
|
45
|
+
document = XML::Parser.string(@http_response.body).parse
|
|
46
|
+
|
|
47
|
+
# FIXME: For some reason document.find_first("code") (or "//code", etc.)
|
|
48
|
+
# and document.find_first("message") return nil, while this works. It
|
|
49
|
+
# makes no sense that this works and that doesn't. Oh well.
|
|
50
|
+
document.root.children.each do |child|
|
|
51
|
+
@type = child.content if child.name == "code"
|
|
52
|
+
@description = child.content if child.name == "message"
|
|
53
|
+
end
|
|
54
|
+
else
|
|
55
|
+
@type = "Unknown"
|
|
56
|
+
@description = @http_response.body.strip
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Public: Mixin that gives a few convenience methods for handling errors in
|
|
62
|
+
# domain objects.
|
|
63
|
+
module ErrorHandler
|
|
64
|
+
# Public: Get/Set the current error in this object.
|
|
65
|
+
attr_accessor :error
|
|
66
|
+
|
|
67
|
+
# Public: Check if this object doesn't have any errors.
|
|
68
|
+
def valid?
|
|
69
|
+
error.nil?
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/azure/queues.rb
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
require "nokogiri"
|
|
2
|
+
require "azure/queues/service"
|
|
3
|
+
require "azure/queues/queue"
|
|
4
|
+
require "azure/queues/message"
|
|
5
|
+
require "azure/queues/service_properties"
|
|
6
|
+
require "azure/queues/uri"
|
|
7
|
+
|
|
8
|
+
module Azure
|
|
9
|
+
module Queues
|
|
10
|
+
# Public: This operation gets the properties of a storage account’s Queue Service.
|
|
11
|
+
# If the operation is unsuccessful the resulting object will have the error
|
|
12
|
+
# accessible through the #error method.
|
|
13
|
+
#
|
|
14
|
+
# options - Options for this query (default: {}):
|
|
15
|
+
# :timeout - (optional) timeout for the request in seconds
|
|
16
|
+
# service - The backend service to implement this (optional).
|
|
17
|
+
#
|
|
18
|
+
# Returns an Azure::Queues::ServiceProperties object
|
|
19
|
+
def self.get_service_properties(options = {}, service=Azure::Queues::Services::GetServiceProperties.new)
|
|
20
|
+
response = service.call(options)
|
|
21
|
+
|
|
22
|
+
if response.success?
|
|
23
|
+
result = Nokogiri::XML(response.body)
|
|
24
|
+
node = result.xpath('//StorageServiceProperties').first
|
|
25
|
+
ServiceProperties.from_node(node)
|
|
26
|
+
else
|
|
27
|
+
ServiceProperties.from_error(response.error)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Public: This operation sets the properties of a storage account’s Queue Service.
|
|
32
|
+
# If the operation is unsuccessful the resulting object will have the error
|
|
33
|
+
# accessible through the #error method.
|
|
34
|
+
#
|
|
35
|
+
# properties - an Azure::Queues::ServiceProperties object
|
|
36
|
+
# options - Options for this query (default: {}):
|
|
37
|
+
# :timeout - (optional) timeout for the request in seconds
|
|
38
|
+
# service - The backend service to implement this (optional).
|
|
39
|
+
#
|
|
40
|
+
# Returns an Azure::Queues::ServiceProperties object
|
|
41
|
+
def self.set_service_properties(properties, options = {}, service=Azure::Queues::Services::SetServiceProperties.new)
|
|
42
|
+
response = service.call(properties, options)
|
|
43
|
+
properties.error = response.error unless response.success?
|
|
44
|
+
response.success?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Public: This operation lists all of the queues in a given storage account.
|
|
48
|
+
# GET http://myaccount.queue.core.windows.net?comp=list
|
|
49
|
+
#
|
|
50
|
+
# options - Options for this query (default: {}):
|
|
51
|
+
# :prefix - Filters the results to return only queues with names that begin with the specified prefix.
|
|
52
|
+
# :marker - A string value that identifies the portion of the list to be returned with the next list operation.
|
|
53
|
+
# :maxresults - Specifies the maximum number of queues to return
|
|
54
|
+
# :include_metadata - Include this parameter to specify that the container's metadata be returned as part of the response body.
|
|
55
|
+
# service - The backend service to implement this (optional).
|
|
56
|
+
#
|
|
57
|
+
# Returns an Array of Query elements.
|
|
58
|
+
def self.all(options = {}, service=Azure::Queues::Services::ListQueues.new)
|
|
59
|
+
response = service.call(options)
|
|
60
|
+
|
|
61
|
+
if response.success?
|
|
62
|
+
result = Nokogiri::XML(response.body)
|
|
63
|
+
result.xpath('//Queues/Queue').map {|node| Queue.from_node(node) }
|
|
64
|
+
else
|
|
65
|
+
[]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Public: Try to create a new queue. If the operation is unsuccessful,
|
|
70
|
+
# the resulting Queue object will have the error accessible through the
|
|
71
|
+
# #error method.
|
|
72
|
+
#
|
|
73
|
+
# name - The name of the container.
|
|
74
|
+
# metadata - User defined metadata for this container (optional).
|
|
75
|
+
# service - The backend service to implement this (optional).
|
|
76
|
+
#
|
|
77
|
+
# Returns a Queue.
|
|
78
|
+
def self.create(name, metadata={}, service=Azure::Queues::Services::CreateQueue.new)
|
|
79
|
+
response = service.call(name, metadata)
|
|
80
|
+
|
|
81
|
+
if response.success?
|
|
82
|
+
Queue.new(name)
|
|
83
|
+
else
|
|
84
|
+
Queue.from_error(response.error)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Public: Delete a Queue from the server. If the delete operation fails,
|
|
89
|
+
# the queue is invalidated. Otherwise it's frozen.
|
|
90
|
+
#
|
|
91
|
+
# queue - An Azure::Queues::Queue.
|
|
92
|
+
# service - The backend service to implement this (optional).
|
|
93
|
+
#
|
|
94
|
+
# Returns true|false to indicate success.
|
|
95
|
+
def self.delete(queue, service=Azure::Queues::Services::DeleteQueue.new)
|
|
96
|
+
response = service.call(queue.name)
|
|
97
|
+
|
|
98
|
+
if response.success?
|
|
99
|
+
queue.freeze
|
|
100
|
+
else
|
|
101
|
+
queue.error = response.error
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
response.success?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Public: Obtain the queue metadata. This updates the queue and
|
|
108
|
+
# changes the metadata internally if successful. If the operation fails, the
|
|
109
|
+
# queue's metadata does not change, and the queue is invalidated.
|
|
110
|
+
#
|
|
111
|
+
# queue - An Azure::Queues::Queue.
|
|
112
|
+
# service - The backend service to implement this (optional).
|
|
113
|
+
#
|
|
114
|
+
# Returns a Hash.
|
|
115
|
+
def self.load_metadata(queue, service=Azure::Queues::Services::GetMetadata.new)
|
|
116
|
+
response = service.call(queue.name)
|
|
117
|
+
|
|
118
|
+
if response.success?
|
|
119
|
+
queue.extract_metadata(response.headers)
|
|
120
|
+
else
|
|
121
|
+
queue.error = response.error
|
|
122
|
+
{}
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Public: Save the current metadata of a queue in the storage service.
|
|
127
|
+
# If the operation fails, the queue is invalidated.
|
|
128
|
+
#
|
|
129
|
+
# queue - An Azure::Queues::Queue.
|
|
130
|
+
# service - The backend service to implement this (optional).
|
|
131
|
+
#
|
|
132
|
+
# Returns true|false to indicate success.
|
|
133
|
+
def self.save_metadata(queue, service=Azure::Queues::Services::SetMetadata.new)
|
|
134
|
+
response = service.call(queue.name, queue.metadata)
|
|
135
|
+
queue.error = response.error unless response.success?
|
|
136
|
+
response.success?
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
## MESSAGES
|
|
140
|
+
|
|
141
|
+
# Public: Try to add a new message to the back of the message queue. If the
|
|
142
|
+
# operation is unsuccessful, the message is invalidated.
|
|
143
|
+
#
|
|
144
|
+
# queue - An Azure::Queues::Queue.
|
|
145
|
+
# message - An Azure::Queues::Message.
|
|
146
|
+
# options - A Hash of options for this operation.
|
|
147
|
+
# :visibilitytimeout - Time in seconds until the message becomes
|
|
148
|
+
# visible in the queue (default: 0).
|
|
149
|
+
# :messagettl - Time-to-live interval for the message in
|
|
150
|
+
# seconds. Can go up to 7 days (the
|
|
151
|
+
# default).
|
|
152
|
+
# service - The backend service to implement this (optional).
|
|
153
|
+
#
|
|
154
|
+
# Returns the message.
|
|
155
|
+
def self.put_message(queue, message, options = {}, service=Azure::Queues::Services::PutMessage.new)
|
|
156
|
+
response = service.call(queue.name, message.text, options)
|
|
157
|
+
|
|
158
|
+
if response.success?
|
|
159
|
+
message.queue = queue
|
|
160
|
+
else
|
|
161
|
+
message.error = response.error
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
message
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Public: Get one or more messages from the front of the queue.
|
|
168
|
+
#
|
|
169
|
+
# FIXME: Handle errors.
|
|
170
|
+
#
|
|
171
|
+
# queue - An Azure::Queues::Queue.
|
|
172
|
+
# options - A Hash of options for this operation (optional):
|
|
173
|
+
# :visibilitytimeout - Update the returned messages'
|
|
174
|
+
# visibilitytimeout to this number (at least
|
|
175
|
+
# 1, the default).
|
|
176
|
+
# :numofmessages - A number between 1 and 32 of messages to
|
|
177
|
+
# return (default: 1).
|
|
178
|
+
# service - The backend service to implement this (optional).
|
|
179
|
+
#
|
|
180
|
+
# Returns an Array of Messages.
|
|
181
|
+
def self.get_messages(queue, options = {}, service = Azure::Queues::Services::GetMessages.new)
|
|
182
|
+
response = service.call(queue.name, options)
|
|
183
|
+
|
|
184
|
+
# FIXME: need error handling
|
|
185
|
+
document = Nokogiri::XML(response.body)
|
|
186
|
+
(document / "//QueueMessagesList/QueueMessage").map do |node|
|
|
187
|
+
Message.from_node(node) { |m| m.queue = queue }
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Public: Peek at one or more messages from the front of the queue.
|
|
192
|
+
#
|
|
193
|
+
# queue - The message queue
|
|
194
|
+
# options - A Hash of options for this operation (optional):
|
|
195
|
+
# :numofmessages - A number between 1 and 32 of messages to return
|
|
196
|
+
# (default: 1).
|
|
197
|
+
# service - The backend service to implement this (optional).
|
|
198
|
+
#
|
|
199
|
+
# Returns an Array of Messages.
|
|
200
|
+
def self.peek_messages(queue, options = {}, service = Azure::Queues::Services::PeekMessages.new)
|
|
201
|
+
response = service.call(queue.name, options)
|
|
202
|
+
|
|
203
|
+
# FIXME: need error handling
|
|
204
|
+
document = Nokogiri::XML(response.body)
|
|
205
|
+
(document / "//QueueMessagesList/QueueMessage").map do |node|
|
|
206
|
+
Message.from_node(node) { |m| m.queue = queue }
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Public: Delete the specified message from the queue. If the operation
|
|
211
|
+
# fails, the message is invalidated.
|
|
212
|
+
#
|
|
213
|
+
# message - An Azure::Queues::Message.
|
|
214
|
+
# service - The backend service to implement this (optional).
|
|
215
|
+
#
|
|
216
|
+
# Returns true|false to indicate success.
|
|
217
|
+
def self.delete_message(message, service = Azure::Queues::Services::DeleteMessage.new)
|
|
218
|
+
response = service.call(
|
|
219
|
+
message.queue.name,
|
|
220
|
+
message.id,
|
|
221
|
+
message.pop_receipt
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
if response.success?
|
|
225
|
+
message.freeze
|
|
226
|
+
else
|
|
227
|
+
message.error = response.error
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
response.success?
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Public: Delete all messages from the queue. If the operation fails, the
|
|
234
|
+
# queue is invalidated.
|
|
235
|
+
#
|
|
236
|
+
# queue - The message queue.
|
|
237
|
+
# service - The backend service to implement this (optional).
|
|
238
|
+
#
|
|
239
|
+
# Returns true|false to indicate success.
|
|
240
|
+
def self.clear_messages(queue, service = Azure::Queues::Services::ClearMessages.new)
|
|
241
|
+
response = service.call(queue.name)
|
|
242
|
+
queue.error = response.error unless response.success?
|
|
243
|
+
response.success?
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Public: Update the specified message. If the operation fails, the message
|
|
247
|
+
# is invalidated.
|
|
248
|
+
#
|
|
249
|
+
# message - An Azure::Queues::Message.
|
|
250
|
+
# service - The backend service to implement this (optional).
|
|
251
|
+
#
|
|
252
|
+
# Returns true|false to indicate success.
|
|
253
|
+
def self.update_message(message, service=Azure::Queues::Services::UpdateMessage.new)
|
|
254
|
+
response = service.call(
|
|
255
|
+
message.queue.name,
|
|
256
|
+
message.id,
|
|
257
|
+
message.text,
|
|
258
|
+
message.visibility_timeout,
|
|
259
|
+
message.pop_receipt
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if response.success?
|
|
263
|
+
message.time_next_visible = response.headers['x-ms-time-next-visible']
|
|
264
|
+
message.pop_receipt = response.headers['x-ms-popreceipt']
|
|
265
|
+
else
|
|
266
|
+
message.error = response.error
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
response.success?
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|