Giter VIP home page Giter VIP logo

azure-sdk-for-ruby's Introduction

[RETIRED] Microsoft Azure SDK for Ruby - Resource Management (preview)

Build Status Code Climate

This project provides a Ruby package for Azure Resource Management (ARM). If you're looking for Azure Service Management (ASM) please refer to this repo

Additional info on Azure deployment models https://docs.microsoft.com/azure/azure-resource-manager/resource-manager-deployment-model

Important Announcement

As of February 2021, Azure Resource Management SDK for Ruby has entered a retirement phase and is no longer officially supported by Microsoft. Here is the complete list of packages that are affected by this. We are committed to making critical security and bug fixes for libraries in this repo until December 31, 2021. After that date, this repo will no longer be maintained.

For current users of the Azure Resource Management SDK for Ruby, we have prepared a migration guide that points outlines different alternative approaches you can take moving forward. Please check the guide here.

Thank you for your support so far. Should you have any question, please feel free to open an issue on GitHub.

Packages that are no longer supported

All resource management Azure Resource Management packages that contains “azure-mgmt” will be retired as well as a few client libraries.

Note that the Azure Storage SDK for Ruby is excluded from this retirement and continues to be maintained. The Azure Storage SDK for Ruby is available in its own preview gem and GitHub repository, which is still being maintained

Below is the list of the packages that are being retired

  • Authorization Azure Resource Manager role based authorization
  • Batch Azure Batch Management Account & Application operations management
  • CDN Azure Content Delivery Network
  • Cognitive Services Azure Cognitive Services Account management
  • Commerce Azure Commerce Usage aggregates & Rate card management
  • Compute Virtual Machines, Images, Extensions, Availability Sets, etc...
  • Consumption Usage details, Operations, etc...
  • Data Lake Analytics Azure Data Lake Analytics Account, Catalog and Job management
  • Data Lake Store Azure Data Lake Store Account and FileSystem management
  • DevTest Labs Azure DevTest Labs LabOperations, ArtifactSourceOperations, ArtifactOperations, CostOperations etc...
  • DNS Azure DNS Record Set and Zone management
  • Features Feature Exposure Controls
  • Graph Azure Active Directory Applications, Users, etc...
  • Monitor Azure Monitor management
  • IoTCentral Azure IoTCentral's Resource Management
  • IoTHub Azure IoTHub's Resource Management
  • Key Vault Azure Key Vault's vault management
  • Locks Management locks for Azure Resources
  • Logic Integration Accounts, AccountSchemas, AccountMaps, AccountPartners etc...
  • Machine Learning Azure Machine Learning web services management
  • Media Services Media Services resource management APIs
  • Mobile Engagement Azure Mobile Engagement's Apps, App Collections, Devices management APIs
  • Managed Service Identity Create, update, list user assigned identities.
  • Network Load Balancers, Network Gateways, Security Groups, etc...
  • Notification Hubs Notification Hubs management
  • Policy Policy Assignment & Policy definition operations
  • Power BI Embedded Azure Power BI Embedded Workspace & WorkspaceCollection management
  • Redis Redis resource management
  • Resources Resource Groups, Resource Providers, Template Deployments, Operations, etc...
  • Resource Graph Query Azure Resource Manager resources at scale
  • Scheduler Manage scheduled jobs in Azure
  • Search Manage Search resources
  • Server Management Azure Server Management Service like node, gateway, powershell etc..
  • SQL Manage Azure SQL resources
  • Storage Account Creation, Usage Operations, etc...
  • Stream Analytics Create, update, start, stop streaming jobs etc...
  • Subscriptions Manage Azure subscriptions
  • Traffic Manager Azure Traffic Manager's profile & endpoint management
  • WebApps Manage WebApps, formally known as WebSites

Azure Services

Supported Ruby Versions

  • Ruby 2+

Note: x64 Ruby for Windows is known to have some compatibility issues.

Getting Started with Azure Resource Manager Usage (Preview)

Install the rubygem packages

You can install the azure rubygem packages directly.

gem install azure_mgmt_compute
gem install azure_mgmt_storage
gem install azure_mgmt_resources
gem install azure_mgmt_network

Or use them in your Gemfile.

gem 'azure_mgmt_storage'
gem 'azure_mgmt_compute'
gem 'azure_mgmt_resources'
gem 'azure_mgmt_network'

Be aware the Azure Resource Manager Ruby SDK is in preview and will likely have breaking interface changes in upcoming releases. An increased number in Minor version may indicate breaking changes.

Authentication

The first step to using the SDK is authentication and permissions. For people unfamilar with Azure this may be one of the more difficult concepts. For a reference on setting up a service principal from the command line see Authenticating a service principal with Azure Resource Manager or Unattended Authentication. For a more robust explanation of authentication in Azure, see Developer’s guide to auth with Azure Resource Manager API.

After creating the service principal, you should have three pieces of information, a client id (GUID), client secret (string) and tenant id (GUID) or domain name (string).

Prerequisite

In order to use the Azure SDK, you must supply the following values to the Azure SDK:

  • Tenant Id
  • Client Id
  • Subscription Id
  • Client Secret

You could pass the above values in the following ways:

Option 1 - Environment Variables

You can set the (above) values using the following environment variables:

  • AZURE_TENANT_ID
  • AZURE_CLIENT_ID
  • AZURE_SUBSCRIPTION_ID
  • AZURE_CLIENT_SECRET

To set the environment variables, in Windows, you could use the command such as:

set AZURE_TENANT_ID=<YOUR_TENANT_ID>

In Unix based systems, you could use the command such as:

export AZURE_TENANT_ID=<YOUR_TENANT_ID>

Option 2 - Options Hash

The initialization of profile clients take an options hash as a parameter. This options hash consists of tenant_id, client_id, client_secret, subscription_id, active_directory_settings and credentials. Among these, the active_directory_settings and credentials are optional.

You can set the (above) values using the options hash:

options = {
  tenant_id: 'YOUR TENANT ID',
  client_id: 'YOUR CLIENT ID',
  client_secret: 'YOUR CLIENT SECRET',
  subscription_id: 'YOUR SUBSCRIPTION ID'
}

If you would like to pass in the credentials object, you could use the the following code:

provider = MsRestAzure::ApplicationTokenProvider.new(
       'YOUR TENANT ID',
       'YOUR CLIENT ID',
       'YOUR CLIENT SECRET')
credentials = MsRest::TokenCredentials.new(provider)

options = {
  tenant_id: 'YOUR TENANT ID',
  client_id: 'YOUR CLIENT ID',
  client_secret: 'YOUR CLIENT SECRET',
  subscription_id: 'YOUR SUBSCRIPTION ID',
  credentials: credentials
}

Option 3 - Combination of Environment Variables & Options Hash

You can set the (above) values using a combination of environment variables and options hash. The values mentioned in the options hash will take precedence over the environment variables.

Azure Multiple API versions & Profiles

With 0.15.0 of Azure SDK, multiple API versions and profiles are introduced. With these changes, each individual gem has multiple versions of the services and several profiles. The azure_sdk rollup gem also consists of several profiles. The following section provides details on the usage of multiple API versions and profiles.

Why Multiple API versions?

Versions 0.14.0 and older, would have access to the latest versions of Azure services at the time of release. Each release up to 0.14.0, would include the latest available api-version of the services. There wasn't an option to use an older api-version of the service (except for using an older version of the sdk gem). With the introduction of multiple API versions per gem, any api-version available for the service can be explicitly targeted.

What is a Profile?

A profile is a combination of different resource types with different versions from different services. Using a profile, will help you mix and match between various resource types.

What to use?

  • If you would like to use the latest versions of all the services, then the recommendation is to use the Latest profile of the Azure SDK rollup gem.

  • If you would like to use the services compatible with the Azure Stack, then the recommendation is to use the V2017_03_09 profile of the Azure SDK rollup gem.

  • If you would like to use the latest api-version of a service, then the recommendation is to use the Latest profile of the specific gem. For example, if you would like to use the latest api-version of compute service alone, use the Latest profile of compute gem.

  • If you would like to use specific api-version of a service, then the recommendation is to use the specific API versions defined inside that gem.

Note: All the above options could be combined within the same application.

Usage of azure_sdk gem

azure_sdk gem is a rollup of all the supported gems in the Ruby SDK. This gem consists of a Latest profile which supports the latest version of all services. it introduces a versioned profile V2017_03_09 profile which is built for Azure Stack.

Install

You can install the azure_sdk rollup gem with the following command:

gem install 'azure_sdk'

Existing Profiles

To start with, the azure_sdk rollup gem has two profiles.

  1. V2017_03_09 (Profile built for Azure Stack)
  2. Latest (Profile consists of Latest versions of all services)

You could choose the profile that you would like to use. If you would like to use the latest versions of all the services, then the recommendation is to use the Latest profile of the azure_sdk rollup gem.

If you would like to use the services compatible with the Azure Stack, then the recommendation is to use the V2017_03_09 profile of the Azure SDK rollup gem.

Usage

The following lines should be used to instantiate a profile client:

# Provide credentials
options = {
  tenant_id: ENV['AZURE_TENANT_ID'],
  client_id: ENV['AZURE_CLIENT_ID'],
  client_secret: ENV['AZURE_CLIENT_SECRET'],
  subscription_id: ENV['AZURE_SUBSCRIPTION_ID']
}

# Target profile built for Azure Stack
profile_client = Azure::Profiles::V2017_03_09::Mgmt::Client.new(options)

The profile client could be used to access individual RPs:

# To access the operations associated with Compute
profile_client.compute.virtual_machines.get 'RESOURCE_GROUP_NAME', 'VIRTUAL_MACHINE_NAME'

# Option 1: To access the models associated with Compute
purchase_plan_obj = profile_client.compute.model_classes.purchase_plan.new

# Option 2: To access the models associated with Compute
# Notice Namespace: Azure::Profiles::<Profile Name>::<Service Name>::Mgmt::Models::<Model Name>
purchase_plan_obj = Azure::Profiles::V2017_03_09::Compute::Mgmt::Models::PurchasePlan.new

Usage of Individual gem using Profiles

Install

You can install the individual gems using gem install. For eg, to install azure_mgmt_compute, use the following command:

gem install 'azure_mgmt_compute'

Usage

The following lines should be used to instantiate a profile client:

# Provide credentials
options = {
  tenant_id: ENV['AZURE_TENANT_ID'],
  client_id: ENV['AZURE_CLIENT_ID'],
  client_secret: ENV['AZURE_CLIENT_SECRET'],
  subscription_id: ENV['AZURE_SUBSCRIPTION_ID']
}

# Target profile built for Latest Compute
profile_client = Azure::Compute::Profiles::Latest::Mgmt::Client.new(options)

The profile client could be used to access operations and models:

# To access the operations associated with Compute
profile_client.virtual_machines.get 'RESOURCE_GROUP_NAME', 'VIRTUAL_MACHINE_NAME'

# Option 1: To access the models associated with Compute
purchase_plan_obj = profile_client.model_classes.purchase_plan.new

# Option 2: To access the models associated with Compute
# Notice Namespace: Azure::<Service Name>::Profiles::<Profile Name>::Mgmt::Models::<Model Name>
purchase_plan_obj = Azure::Compute::Profiles::Latest::Mgmt::Models::PurchasePlan.new

Usage of Individual gem using using specific api-version

In the previous section, we used the profile associated with individual gem. In the current section, we could use the version directly.

Install

You can install the individual gems using gem install. For eg, to install azure_mgmt_compute, use the following command:

gem install 'azure_mgmt_compute'

Usage

The following lines should be used to instantiate a profile client:

# To use this scenario, you must specify the tenant id, client id, subscription id
# and client secret using the environment variables.
# Provide credentials
provider = MsRestAzure::ApplicationTokenProvider.new(
       ENV['AZURE_TENANT_ID'],
       ENV['AZURE_CLIENT_ID'],
       ENV['AZURE_CLIENT_SECRET'])
credentials = MsRest::TokenCredentials.new(provider)

# Target client for 2016_03_30 version of Compute
compute_client = Azure::Compute::Mgmt::V2016_03_30::ComputeManagementClient.new(credentials)
compute_client.subscription_id = ENV['AZURE_SUBSCRIPTION_ID']

The compute client could be used to access operations and models:

# To access the operations associated with Compute
compute_client.virtual_machines.get 'RESOURCE_GROUP_NAME', 'VIRTUAL_MACHINE_NAME'

# To access the models associated with Compute
# Notice Namespace: Azure::<Service Name>::Mgmt::<Version Name>::Models::<Model Name>
purchase_plan_obj = Azure::Compute::Mgmt::V2016_03_30::Models::PurchasePlan.new

Samples using Profiles

The following samples could be used as a reference for Profiles usage::

Getting Started Samples

The tests for the libraries should provide a good example of how to get started with the clients. You can also see the readme for each of the libraries Compute, Network, Storage, or Resources.

For more getting started samples go to Azure-Samples. Please make sure to look for the azure_mgmt_* gem versions for samples.

Contribute Code or Provide Feedback

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Development Environment Setup

Download Source Code

To get the source code of the SDK via git just type:

git clone https://github.com/Azure/azure-sdk-for-ruby.git
cd ./azure-sdk-for-ruby

Then, run bundler to install all the gem dependencies:

bundle install

Run Recorded Integration Tests

  • Set the environment variable INTEG_RECORDED = true
  • Run rake arm:spec

Re-Record Integration Tests

  • Set the environment variable INTEG_RECORDED = false or un-set it
  • Copy .env_sample to .env
  • Update .env with your Azure credentials .env is in the .gitignore, so should only reside locally
  • Run specific test using rspec example:
 cd ./management/azure_mgmt_compute
 rspec spec/2017-03-30/virtual_machines_spec.rb

If the vcr cassette exists, then it'll replay the test, otherwise it'll record it.

Generate Documentation

Running the command yard will generate the API documentation in the ./doc directory.

Provide Feedback

If you encounter any bugs with the library please file an issue in the Issues section of the project. Please make sure to label the issues with either arm or asm to help us expedite the process.

Azure CLI Tooling

For documentation on Azure PowerShell. For documentation on Azure CLI.


This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

azure-sdk-for-ruby's People

Contributors

abelhu avatar andrewelizondo avatar archerzz avatar babumuralidharan avatar bim-msft avatar d-m-u avatar devigned avatar guangyang avatar jcookems avatar jianghaolu avatar kairu-ms avatar microsoft-github-policy-service[bot] avatar mmyyrroonn avatar mrhardikjoshi avatar nagwanidheeraj avatar nicklebedev37 avatar nterry avatar rahim avatar ranjan avatar sangy14 avatar sarangan12 avatar skierkowski avatar stuartpreston avatar tandibar avatar thoward avatar veronicagg avatar vishrutshah avatar yuzorma avatar zachproffitt avatar zevarito avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

azure-sdk-for-ruby's Issues

Security: Validate HTTPS responses

One of the reasons for using HTTPS is that you can be sure that there is no man-in-the-middle, intercepting and potentially altering your requests.

However, the Azure::Core::Http::HttpRequest contains this alarming code:

          if uri.scheme.downcase == 'https'
            # require 'net/https'
            http.use_ssl = true
            http.verify_mode = OpenSSL::SSL::VERIFY_NONE
          end

It appears that the server's SSL key is not verified, which means that if I can hijack your router, then all your HTTPS traffic is as open as HTTP traffic.

Duplicate/Dead shared_key* classes

The code base has two versions of SharedKey and SharedKeyLite, but only has unit tests for one version.

c:\dd\git\jcooke\azure-sdk-for-ruby>grep -lr SharedKey * | grep -v doc/
lib/azure/core/auth/authorizer.rb
lib/azure/core/auth/shared_key.rb
lib/azure/core/auth/shared_key_lite.rb
lib/azure/core/signed_service.rb
lib/azure/service/storage_service.rb
lib/azure/table/auth/shared_key.rb
lib/azure/table/auth/shared_key_lite.rb
lib/azure/table/table_service.rb
test/unit/core/auth/shared_key_lite_test.rb
test/unit/core/auth/shared_key_test.rb

We should consolidate these two versions.

GB18030: Consider adding UTF-8 conversion code to XML serializer

There are many places in our code where user strings get passed directly to the XML serializer. For example:

subject { Azure::ServiceBus::ServiceBusService.new }
subject.create_queue queue_name, {
  :dead_lettering_on_message_expiration =>
     "\u2488\u2460\u216B\u3128\u3129".encode("GB18030"),
}

This leads to this console output:

output error : string is not in UTF-8

and the XML output in the request:

<QueueDescription ...>
  <DeadLetteringOnMessageExpiration></DeadLetteringOnMessageExpiration>
</QueueDescription>

Notice that the inner tag is empty, which could lead to confusion to people trying to debug issues.

EXPECT: As a part of our XML serializer, we automatically convert strings to UTF-8 (unless they are some binary format that should remain unchanged.)

Consider adding more properties to Azure::Storage:Blob::Blob

The Azure REST API doesn’t define properties like 'uri' in the response. See http://msdn.microsoft.com/en-us/library/windowsazure/jj732965.aspx. I see they are defined in the .NET client library. See http://msdn.microsoft.com/en-us/library/windowsazure/jj732965.aspx. And there are a bunch of other properties defined in the .NET client library.

I guess if we want to add this property, we need to generate the uri by ourselves. We can consider this in the next release.

Add to_json method to the model classes

It's a common scenario to convert an object from/to json in Ruby applications. Usually people will expect a to_json method on an object. We should consider add it to the model classes we have.

Azure::ServiceBus::ServiceBus.create_subscription() returns subscription object with most of the attributes empty.

Following is what you will get as the return value of create_subscription().

author_name: 
description: {}
id: 
name: test-subscription-1
published: 
updated: 
dead_lettering_on_message_expiration: 
default_message_ttl: 
enable_batched_operations: 
lock_duration: 
max_delivery_count: 
message_count: 
ordered_props: ["LockDuration", "RequiresSession", "DefaultMessageTimeToLive", "DeadLetteringOnMessageExpiration", "DeadLetteringOnFilterEvaluationExceptions"]
requires_session: 

However, if you call get_subscription(), it will return

author_name: 
description: {"LockDuration"=>"PT1M", "RequiresSession"=>"false", "DefaultMessageTimeToLive"=>"P10675199DT2H48M5.4775807S", "DeadLetteringOnMessageExpiration"=>"false", "DeadLetteringOnFilterEvaluationExceptions"=>"true", "MessageCount"=>"0", "MaxDeliveryCount"=>"10", "EnableBatchedOperations"=>"true"}
id: sb://rorvmtest.servicebus.windows.net/test-topic-1/subscriptions/test-subscription-1
name: test-subscription-1
published: 2013-01-09 01:33:30 UTC
updated: 2013-01-09 01:33:30 UTC
dead_lettering_on_message_expiration: false
default_message_ttl: P10675199DT2H48M5.4775390625S
enable_batched_operations: true
lock_duration: PT1M
max_delivery_count: 10
message_count: 0
ordered_props: ["LockDuration", "RequiresSession", "DefaultMessageTimeToLive", "DeadLetteringOnMessageExpiration", "DeadLetteringOnFilterEvaluationExceptions"]
requires_session: false

which is the expected result.

Table: Cannot use GB-18030 characters in strings

Dev Estimate: 2
Test Estimate: 1
Ranking: 200

To be GB-18030 compliant, the Ruby SDK needs to support Hangul characters. For example, "다", a.k.a. Unicode codepoint U+B2E4, represented as 0xEB 0x8B 0xA4 in UTF-8.

When I update Azure::Storage::Table::TableService::#insert_entity with this code:

    let(:entity_properties){ 
      { 
        "CustomStringProperty" => "\xEB\x8B\xA4 U+B2E4",
      }
    }

The XML emitted looks good (at first glance), Ruby is smart enough to encode the character using XML escaping:

    <m:properties>
      <d:PartitionKey m:type="">testingpartition</d:PartitionKey>
      <d:RowKey m:type="">abcd123</d:RowKey>
      <d:CustomStringProperty m:type="Edm.Binary">&#xB2E4; U+B2E4</d:CustomStringProperty>
    </m:properties>

But it is wrong, the type is "BINARY", when it should be treated as a STRING. The Azure service notices this and is not happy about the switch:

HTTP/1.1 400 Bad Request
  <code>InvalidInput</code>
  <message xml:lang="en-US">One of the request inputs is not valid.</message>

It is incorrect to treat this data a binary; that will impact interop with other languages, if multiple SDKs are targeting the same table store.

I'm not sure how to fix this issue, it is more of a design flaw.

GB18030: Table property names and string values need to be converted to UTF-8

Like in #295, without re-encoding property names, they will be embedded in the output XML with their original encoding, so any GB-18030 encoded string will not be interpreted correctly by the server.

        prop = "prop" + [0x3128, 0x3129].pack('U*').encode!("GB18030")
        entity_properties = { 
          "PartitionKey" => "x",
          "RowKey" => k
          prop => "value",
        }
        result = subject.insert_entity table_name, entity_properties

The XML going out across the wire retains is original GB-18030 encoding (0xA8 0xE8 0xA8 0xE9), which causes the server to complain:

HTTP/1.1 400 Bad Request
...
  <code>InvalidInput</code>
  <message xml:lang="en-US">One of the request inputs is not valid.
RequestId:2c079ac8-c761-4227-8fe1-24f3291fa8e3
Time:2013-03-18T17:55:23.8220736Z</message>
</error>

GB18030: Consider using string encoding information when uploading/downloading from blobs

If I upload a GB-18030 encoded string to a blob, then download it, it will be considered different, because the encoding information is lost.

content = "啊阿鼾齄".encode!("GB18030")
subject.create_block_blob container_name, blob_name, content, options
blob, returned_content = subject.get_blob container_name, blob_name
p returned_content.encoding
p returned_content
p content.encoding
p content
returned_content.must_equal content

This fails and outputs:

#<Encoding:ASCII-8BIT>
"\xA2\xB1\xA2\xD9\xA2\xFC\xA8\xE8\xA8\xE9"
#<Encoding:GB18030>
"\x{A2B1}\x{A2D9}\x{A2FC}\x{A8E8}\x{A8E9}"

This could be mitigated by the user by adding code this this:

options = { :content_encoding=>"GB18030" }
...
returned_content.force_encoding(blob.properties[:content_encoding])

But it would be a nice feature if the create_*_blob and get_blob methods could automatically add the encoding info to\from strings going in\out of the blob.

Mock UT

This can be done after the initial release

Missing conditional headers for some Blob service operations

According to Specifying Conditional Headers for Blob Service Operations, the blob's acquire_lease method should support the conditional headers.

I assembled the following table from that Web page for auditing the PHP and Java blobs; it might be helpful for auditing the Ruby methods too:

REST OperationIf-Modified-SinceIf-Unmodified-SinceIf-MatchIf-None-Match
Create ContainerN/AN/AN/AN/A
Delete ContainerxxN/AN/A
Get Container MetadataN/AN/AN/AN/A
Set Container MetadataxN/AN/AN/A
Get Container PropertiesN/AN/AN/AN/A
Get Container ACLN/AN/AN/AN/A
Set Container ACLN/AN/AN/AN/A
List BlobsN/AN/AN/AN/A
Get Blob Metadataxxxx
Set Blob Metadataxxxx
Get Blob PropertiesxxN/AN/A
Set Blob Propertiesxxxx
Put Blobxxxx
Get Blobxxxx
Copy Blobxxxx
Snapshot Blobxxxx
Lease Blobxxxx
Delete Blobxxxx
Put BlockN/AN/AN/AN/A
Get Block Listxxxx
Put Block Listxxxx
Put Pagexxxx
Get Page Rangesxxxx

For example, I just used it to find that the delete_blob is missing :if_modified_since and :if_unmodified_since.

Feature request: Add support for client-side timeout

It is possible for an HTTP call to never return. When that happens, the client needs to know when to give up, using a client-side timeout. See this Java code that adds the client-timeout support for ideas.

Note: PHP does not have this feature, and neither did Java for the first 8 months.

GB18030: Table entity keys need to be converted to UTF-8 when used to construct URL

There are a few places where the an entity's keys are placed into the URL, which all appear to go through this:
``ruby
return table_name if table_name.kind_of? ::URI

    path = if partition_key && row_key
      "%s(PartitionKey='%s',RowKey='%s')" % [
        table_name, encodeODataUriValue(partition_key), encodeODataUriValue(row_key)
      ]
However, `encodeODataUriValue` simply URI encodes the value without first converting to UTF-8, which is required by by the server.

To make this concrete, when uploading this value:
```xml
    <m:properties>
      <d:PartitionKey>value⒈①Ⅻㄨㄩ</d:PartitionKey>
      <d:RowKey>value⒈①Ⅻㄨㄩ</d:RowKey>
    </m:properties>

Then when querying for this using get_entity, using the GB-18030 encoding, have this URL:

http://...cscizktpdn(PartitionKey='value%A2%B1%A2%D9%A2%FC%A8%E8%A8%E9',RowKey='value%A2%B1%A2%D9%A2%FC%A8%E8%A8%E9')

which is remains encoded as GB-18030, instead of UTF-8.

Test issue: Blob test code uses TableNameHelper.clean, should be blob-appropriate

Blob test code should use blob clean methods, not table clean methods.

$ grep -r TableNameHelper * | grep -v doc
test/integration/storage/blob/blob_metadata_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/blob_pages_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/blob_properties_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/block_blob_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/container/container_acl_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/container/container_metadata_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/container/create_container_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/container/delete_container_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/container/get_container_properties_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/container/list_containers_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/copy_blob_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/create_blob_snapshot_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/create_page_blob_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/delete_blob_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/get_blob_test.rb:  after { TableNameHelper.clean }
test/integration/storage/blob/informative_errors_test.rb:    after { TableNameHelper.clean }
test/integration/storage/blob/list_blobs_test.rb:  after { TableNameHelper.clean }
<rest are for table tests>

Fix unperformant cleanup code NameGenerator

The Table integration tests run slower and slower as the tests progress. I took a look at what was happening with Fidder, and saw that the same tables were getting deleted over and over again.

It looks like the issue is that there is a single stateful TableNameHelper helper class that stores all table names in an array, which it can then delete. This is fine, but the array doesn't get cleared. Some fixes:

  1. Perhaps the code in name_generator.rb should be removing the items from the array, but it fails for some reason (I don't know enough Ruby to be sure about the details of this code):

      @names.reject! do |name|
        @cleanup_proc.call(name)
      end
  2. Could just empty the array explicitly at the end of the loop, which is just straightforward:

      @names = []

Service Bus: Support for alternate endpoints

The Azure::Core::Configuration class only supports the main Azure Service Bus endpoints:

      def acs_host
        "https://#{acs_namespace}-sb.accesscontrol.windows.net"
      end
      def service_bus_host
        "https://#{acs_namespace}.servicebus.windows.net"
      end

However, Service Bus can be hosted in a private cloud, in which case alternate endpoints need to be specified. For example, in addition to just specifying the namespace, like this:

set AZURE_SERVICEBUS_NAMESPACE=jcooketest
set AZURE_SERVICEBUS_ACCESS_KEY=CgX...
set AZURE_SERVICEBUS_ISSUER=owner

we should be able to set the endpoints explicitly, like this:

set AZURE_SERVICEBUS_URI=https://jcooketest.servicebus.windows.net/
set AZURE_SERVICEBUS_WRAP_URI=https://jcooketest-sb.accesscontrol.windows.net/WRAPv0.9
set AZURE_SERVICEBUS_ACCESS_KEY=CgX...
set AZURE_SERVICEBUS_ISSUER=owner

Also, the service_bus_host should include the WRAP0.9 part, instead of hardcoding that into the lib/azure/service_bus/auth/wrap_service.rb

SB: SqlFilter and SqlRuleAction should have convenience constructors

The SqlFilter and SqlRuleAction classes have just two properties, and only one that is commonly set, the SqlExpression property. However, the constructors for those classes only take hashes, and there is no doc comments describing what should be passed. I had to look at the source code to figure out that I need to type:

rule4a.action = Azure::ServiceBus::SqlRuleAction.new(
  {
    "SqlExpression" => 'SET trueRuleA=TRUE; '
  })
rule3.filter = Azure::ServiceBus::SqlFilter.new(
  { 
    "SqlExpression" => 'name LIKE \'%3\' OR user.even = TRUE'
  })

As part of Ruby's design is terseness, I would expect that there would be an "overload" on those constructors like:

      def initialize(hash=nil)
      def initialize(sql_expression, compatibility_level = 20)

Then I could write code like this:

rule4a.action = Azure::ServiceBus::SqlRuleAction.new('SET trueRuleA=TRUE;')
rule3.filter = Azure::ServiceBus::SqlFilter.new('name LIKE \'%3\' OR user.even = TRUE')

Reconsider use of _*_args methods in public service_bus methods

The service_code has several public methods that take a single *p parameter, to allow overloading parameters:

  • _rule_args takes (topic_name, subscription_name, rule_name) as strings, or (rule) a object with those properties, and an options instance.
  • _subscription_args takes (topic_name, subscription_name) as strings, or (subscription) a object with those properties, and an options instance.

This is convenient in some circumstances, but I worry about how it is inconsistent. I would expect one of the following:

  1. All methods take a fixed list of parameters, with no overloads.
  2. All methods that take parts of existing classes as inputs also take those classes as inputs in place of the individual parts.

Currently, we have a mix, at least 90% in case 1, and just a few in case 2. This makes it hard to reason about how to use the API.

Suggestions, in order of my preference:

  1. Remove the overloads for Service Bus methods, for consistency.with the rest of the Blob, Queue, and Table APIs. After shipping, we can figure out how to consistently add overloads.
  2. Leave as is.
  3. Add overloads everywhere now, before shipping. (But this is crazy.)

Add travis CI support

Our SDK does not specify which version of the dependencies it requires. So we should make sure that the SDK keeps working with current versions.

Pri 0: just add travis support

The "Class and Module Index" has duplicates in rdoc-generated docs

After running rdoc, the ...\azure-sdk-for-ruby\doc\Azure.html file's "Class and Module Index" list has duplicate entries for the following items:

  • Azure::ServiceBus::ServiceBusService
  • Azure::Storage::Blob::BlobService
  • Azure::Storage::Queue::QueueService
  • Azure::Storage::Table::TableService

Table: Refactor nested ternary operators

Ternary operators are great in limited doses, but nesting them leads to hard-to-maintain code. Two places that I found in the table code are:

lib/azure/storage/table/batch_response.rb:          
  (match_boundary and not match_end)? :boundary : (match_end? :end : nil)
lib/azure/storage/table/serialization.rb:              
  properties[prop.name] = prop.text != ""? cast_property(prop.text, prop["type"]) : prop['null']? nil : ""

These should be refactored.

GB18030: Blob names need to be converted to UTF-8 before being URL encoded

According to Naming and Referencing Containers, Blobs, and Metadata

Certain characters must be percent-encoded to appear in a URL, using UTF-8 (preferred) or MBCS.

So, if we have strings with the encoding set to something other than UTF-8, theny first need to be converted to the correct encoding. For example,

# Chinese2B1 characters, encoded using GB-18030
test_name = [0x2488, 0x2460, 0x216B, 0x3128, 0x3129].pack('U*').encode!("GB18030")
p test_name
p test_name.encoding 

subject.create_block_blob container_name, test_name, "hi"
blobs = subject.list_blobs container_name
blobs.each { |v|
  v.name.must_equal test_name
}
subject.delete_blob container_name, test_name

The test fails because the blob names don't correctly roundtrip.

Release activities

  • Cut release branch
  • Publish the ruby gem (make Microsoft the owner of the azure rubygem)
  • Create WebPI feed (Chain up the ruby environment - ruby runtime, powershell, etc. as of node)
  • Smoke test the rubygem and WebPI feed
  • Ruby dev center goes live
  • Merge and release branch to master and tag the branch
  • Make azure-sdk-for-ruby public
  • Marketing

Blob and Queue: Signing fails for metadata where values contain \n

According to the HTTP spec and grammar, a header value has this format:

field-value    = *( field-content | LWS )
field-content  = <the OCTETs making up the field-value
                        and consisting of either *TEXT or combinations
                        of token, separators, and quoted-string>
TEXT           = <any OCTET except CTLs,
                        but including LWS>
CTL            = <any US-ASCII control character
                        (octets 0 - 31) and DEL (127)>
LWS            = [CRLF] 1*( SP | HT )

The HTTP library that Ruby uses accepts header values that do not conform to those rules, which leads to unexpected behavior in some edge cases. Consider this specially crafted metadata:

let(:metadata) { {
    'foo2' => "bar2\nX-Ms-Meta-Foo4: baz",
    'foo' =>  'bar',
    'foo3' => 'bar3' } }

This gets sent to the server as

X-Ms-Meta-Foo2: bar2
X-Ms-Meta-Foo4: baz
X-Ms-Meta-Foo: bar
X-Ms-Meta-Foo3: bar3

Then the submit fails due signing errors, (because the client signs something with three headers while the server signs four headers). This type of error is very hard to customers to debug.

One way to give customers a better error experience it to fail early, once an invalid header value starts getting processed. In PHP, the implementation is like this:

public function generateMetadataHeaders($metadata) {
  foreach ($metadata as $key => $value) {
    $headerName = Resources::X_MS_META_HEADER_PREFIX;
    if (strpos($value, "\r") !== false || strpos($value, "\n") !== false) {
      throw new \InvalidArgumentException(Resources::INVALID_META_MSG);

(Not ideal, but better than nothing). Ruby should add something similar.

Table: XML nodes for strings should have m:type="Edm.String" or no m:type attribute, not m:type=""

Looking at the XML produced by some of the Ruby table tests, we see:

  <content type="application/xml">
    <m:properties>
      <d:PartitionKey m:type="">part1</d:PartitionKey>
      <d:RowKey m:type="">entity-3</d:RowKey>
      <d:CustomStringProperty m:type="">CustomPropertyValue</d:CustomStringProperty>
      <d:CustomIntegerProperty m:type="Edm.Int32">37</d:CustomIntegerProperty>
      <d:CustomBooleanProperty m:type="Edm.Boolean">true</d:CustomBooleanProperty>
      <d:CustomDateProperty m:type="Edm.DateTime">2012-12-06T15:41:27-08:00</d:CustomDateProperty>
    </m:properties>
  </content>

The disturbing part is the m:type="" for string valued nodes. The attribute should either be elided or have the value "Edm.String", an empty string is not part of the spec, and could be broken if the server decided that it is an error in the future.

Either:

  1. Fill in the value for the attribute, or
  2. Remove the attribute for strings.

Trim whitespace from test inputs from environment variables

None of the test ENV variable values are allowed to have beginning or ending whitespace, but it is easy to accidentally enter trailing whitespace from the command line. For example, on my Win8 box:

REM RUBY
set AZURE_STORAGE_ACCOUNT=jcooketest 
set AZURE_STORAGE_ACCESS_KEY=cLA1oIj4L4nElnK6IdPqHppsglpBiVXd95Qzx+i2XXX...
set AZURE_BLOB_HOST=http://jcooketest.blob.core.windows.net
set AZURE_QUEUE_HOST=http://jcooketest.queue.core.windows.net
set AZURE_TABLE_HOST=http://jcooketest.table.core.windows.net
set AZURE_SERVICEBUS_NAMESPACE=jcooketest
set AZURE_SERVICEBUS_ACCESS_KEY=CgXCeMjmTpx740Uw8lxxx...
set AZURE_SERVICEBUS_ISSUER=owner

In particular, I spent too long trying to figure out what was wrong with the scenario tests. Wrong version of Ruby? Are all dependencies up to date? Is my corp-net proxy eating something? No, just a space at the end of the second line :-(

To make the ramp-up as easy as possible for new users, the ENV reading code should just trim the disallowed whitespace from user input.

Table: Test perf: Consider using a batch insert when initializing test with multiple entities

In the query_entities_tests, there is this code:

    before { 
      subject.create_table table_name
      partitions.each { |p|
        entities[p].each { |e| 
          subject.insert_entity table_name, p, e, entity_properties
        }
      }
    }

It would be somewhat more performant to replace the entity loop with a batch (which would also exercise the batch insert too)

    before { 
      subject.create_table table_name
      partitions.each { |p|
        batch = Azure::Storage::Table::Batch.new table_name, p
        entities[p].each { |e| 
          batch.insert e, entity_properties
        }
        subject.execute_batch batch
      }
    }

Service Bus: Support for alternate endpoints

The Azure::Core::Configuration class only supports the main Azure Service Bus endpoints:

      def acs_host
        "https://#{acs_namespace}-sb.accesscontrol.windows.net"
      end
      def service_bus_host
        "https://#{acs_namespace}.servicebus.windows.net"
      end

However, Service Bus can be hosted in a private cloud, in which case alternate endpoints need to be specified. For example, in addition to just specifying the namespace, like this:

set AZURE_SERVICEBUS_NAMESPACE=jcooketest
set AZURE_SERVICEBUS_ACCESS_KEY=CgX...
set AZURE_SERVICEBUS_ISSUER=owner

we should be able to set the endpoints explicitly, like this:

set AZURE_SERVICEBUS_URI=https://jcooketest.servicebus.windows.net/
set AZURE_SERVICEBUS_WRAP_URI=https://jcooketest-sb.accesscontrol.windows.net/WRAPv0.9
set AZURE_SERVICEBUS_ACCESS_KEY=CgX...
set AZURE_SERVICEBUS_ISSUER=owner

Also, the service_bus_host should include the WRAP0.9 part, instead of hardcoding that into the lib/azure/service_bus/auth/wrap_service.rb

Ruby 2.0: Failures for Blob and Queue

Something is going wrong for the signing when running tests against Ruby 2.0.

The string that is getting signed is:

PUT


0








x-ms-date:Wed, 20 Mar 2013 05:58:48 GMT
x-ms-version:2012-02-12
/jcooketest/ntzqevsxwa

While the server is expecting:

PUT


0

application/x-www-form-urlencoded






x-ms-date:Wed, 20 Mar 2013 05:58:48 GMT
x-ms-version:2012-02-12
/jcooketest/ntzqevsxwa

For some reason, the server is getting a application/x-www-form-urlencoded that the SDK is not expecting.

This does not repro for Table (uses different SharedKeyLite) or for Service Bus (uses ACS).

Table test failures when using latest dependencies

When I run the tests, after installing the Chinese Simplified LIP, and set it as my primary OS language, I get failures. They seem to all come from failures in ...\table\serialization.rb, in particular line 97:

properties[prop.name] = prop.text != "" ?
    Azure::Table::EdmType.unserialize_query_value(prop.text, prop["m:type"]) : 
    prop["null"] ? nil : ""

The problem is that prop["m:type"] is returning nil, so the type information is ignored and all; values are deserialized as strings.

I can fix the issue by removing the "m" namespace, but that is a hack.

Failures:

  1) Failure:
test_0001_creates_an_entity(Azure::Table::TableService::#insert_entity) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/integration/table/insert_entity_test.rb:57]:
Expected: 3.141592
  Actual: "3.141592"

  2) Failure:
test_0001_creates_an_entity(Azure::Table::TableService::#insert_entity_batch) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/integration/table/insert_entity_batch_te
st.rb:61]:
Expected: 3.141592
  Actual: "3.141592"

  3) Failure:
test_0002_updates_an_existing_entity_merging_the_properties(Azure::Table::TableService::#insert_or_merge_entity) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/integ
ration/table/insert_or_merge_entity_test.rb:109]:
Expected: 37
  Actual: "37"

  4) Failure:
test_0001_creates_an_entity_if_it_does_not_already_exist(Azure::Table::TableService::#insert_or_merge_entity) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/integrat
ion/table/insert_or_merge_entity_test.rb:64]:
Expected: 37
  Actual: "37"

  5) Failure:
test_0001_creates_an_entity_if_it_does_not_already_exist(Azure::Table::TableService::#insert_or_merge_entity_batch) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/in
tegration/table/insert_or_merge_entity_batch_test.rb:66]:
Expected: 37
  Actual: "37"

  6) Failure:
test_0002_updates_an_existing_entity_merging_the_properties(Azure::Table::TableService::#insert_or_merge_entity_batch) [C:/dd/git/jcooke/azure-sdk-for-ruby/test
/integration/table/insert_or_merge_entity_batch_test.rb:116]:
Expected: 37
  Actual: "37"

  7) Failure:
test_0001_creates_an_entity_if_it_does_not_already_exist(Azure::Table::TableService::#insert_or_replace_entity) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/integr
ation/table/insert_or_replace_entity_test.rb:64]:
Expected: 37
  Actual: "37"

  8) Failure:
test_0001_creates_an_entity_if_it_does_not_already_exist(Azure::Table::TableService::#insert_or_replace_entity_batch) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/
integration/table/insert_or_replace_entity_batch_test.rb:68]:
Expected: 37
  Actual: "37"

  9) Failure:
test_0001_updates_an_existing_entity_merging_the_properties(Azure::Table::TableService::#merge_entity) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/integration/tab
le/merge_entity_test.rb:73]:
Expected: 37
  Actual: "37"

 10) Failure:
test_0001_updates_an_existing_entity_merging_the_properties(Azure::Table::TableService::#merge_entity_batch) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/integrati
on/table/merge_entity_batch_test.rb:77]:
Expected: 37
  Actual: "37"

 11) Failure:
test_0002_can_constrain_by_partition_and_row_key_returning_zero_or_one_entity(Azure::Table::TableService::#query_entities) [C:/dd/git/jcooke/azure-sdk-for-ruby/
test/integration/table/query_entities_test.rb:88]:
Expected: 37
  Actual: "37"

 12) Failure:
test_0003_can_project_a_subset_of_properties_populating_sparse_properties_with_nil(Azure::Table::TableService::#query_entities) [C:/dd/git/jcooke/azure-sdk-for-
ruby/test/integration/table/query_entities_test.rb:104]:
Expected: 37
  Actual: "37"

 13) Failure:
test_0001_queries_a_table_for_list_of_entities(Azure::Table::TableService::#query_entities) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/integration/table/query_en
tities_test.rb:68]:
Expected: 37
  Actual: "37"

 14) Failure:
test_0007_can_combine_projection_filtering_and_paging_in_the_same_query(Azure::Table::TableService::#query_entities) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/i
ntegration/table/query_entities_test.rb:174]:
Expected: 37
  Actual: "37"

 15) Failure:
test_0007_can_combine_projection_filtering_and_paging_in_the_same_query(Azure::Table::TableService::#query_entities) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/i
ntegration/table/query_test.rb:227]:
Expected: 37
  Actual: "37"

 16) Failure:
test_0002_can_constrain_by_partition_and_row_key_returning_zero_or_one_entity(Azure::Table::TableService::#query_entities) [C:/dd/git/jcooke/azure-sdk-for-ruby/
test/integration/table/query_test.rb:96]:
Expected: 37
  Actual: "37"

 17) Failure:
test_0003_can_project_a_subset_of_properties_populating_sparse_properties_with_nil(Azure::Table::TableService::#query_entities) [C:/dd/git/jcooke/azure-sdk-for-
ruby/test/integration/table/query_test.rb:118]:
Expected: 37
  Actual: "37"

 18) Failure:
test_0001_queries_a_table_for_list_of_entities(Azure::Table::TableService::#query_entities) [C:/dd/git/jcooke/azure-sdk-for-ruby/test/integration/table/query_te
st.rb:71]:
Expected: 37
  Actual: "37"

GB18030: Queue content needs to be converted to UTF-8 before serializing

GB-18030-encoded content cannot be sent to a queue, as this example shows. (It is likely that other encodings will also fail):

content = [0x554A, 0x963F, 0x9F3E, 0x9F44].pack('U*').encode!("GB18030")
subject.create_message queue_name, content
messages = subject.list_messages queue_name, 500
message = messages.first
returned_content = message.message_text
returned_content.must_equal content
subject.delete_message queue_name, message.id, message.pop_receipt

Looking at the network traffic, this is the request that is sent out:

<?xml version="1.0"?>
<QueueMessage>
  <MessageText></MessageText>
</QueueMessage>

By performing an explicit conversion prior to sending out

subject.create_message queue_name, content.encode!("UTF-8")

the message gets sent correctly:

<QueueMessage>
  <MessageText>AB&#x554A;&#x963F;&#x9F3E;&#x9F44;</MessageText>
</QueueMessage>

NOTE: The encode! method is appears to be destructive in-place conversion. To avoid corrupting user data, I suppose that making a copy of the string first would be a better approach.

GB18030: Table query parameters need to be converted to UTF-8 before being URL encoded

Like in #293, the query parameters for tables must be percent-encoded UTF-8 representations of the characters. However, if a user passes in a string encoded with a different encoding (like GB-18030), it byte representation will be used in the percent-encoding, which is not abiding by the server's protocol.

For example (and assuming that #297 is fixed):

prop = "prop" + [0x3128, 0x3129].pack('U*').encode!("GB18030")
entity_properties = { 
   "PartitionKey" => "x",
   "RowKey" => k
   prop => "value",
}
result = subject.insert_entity table_name, entity_properties
filter = prop + " eq 'value'"
result = subject.query_entities table_name, { :filter => filter }
result.length.must_equal 1
result.first.properties[prop].must_equal "value"

This should pass, but it fails with:

  1) Error:
test_0001_read_write_and_query_property_names_gb18030(Table GB-18030):
Azure::Core::Http::HTTPError: InvalidInput (400): One of the request inputs is not valid.

This is because the URL is:

http://...ryn()?$filter=prop%A8%E8%A8%E9+eq+%27value%27

GB18030: Use explicit encoding for all XML

Several of the places where the Nokogiri builder is used, it does not specify an encoding. For definiteness, it should always be called like this:

builder = Nokogiri::XML::Builder.new(:encoding=>"utf-8")

Blob and Queue: Signing fails for metadata where values are padded with spaces

I updated one of the tests to use this metadata when creating a blob:

let(:metadata) { { 
    "CustomMetadataProperty"=>"CustomMetadataValue",
    "foo2' => "bar2   ",
    "foo" =>  "bar",
    "foo3" => "bar3" } }

The request strips the spaces from the values, according to the HTTP spec

Such leading or trailing [white space] MAY be removed without changing the semantics of the field value.

X-Ms-Meta-Custommetadataproperty: CustomMetadataValue
X-Ms-Meta-Foo2: bar2
X-Ms-Meta-Foo: bar
X-Ms-Meta-Foo3: bar3

But the server rejects the client's computed signature:

HTTP/1.1 403 Forbidden
The MAC signature found in the HTTP request is not the same as any computed signature.

This is because the client computes the signature on the user input, which is not the same as what the server gets sent. Here is what gets signed:

x-ms-meta-custommetadataproperty:CustomMetadataValue\n
x-ms-meta-foo:bar\n
x-ms-meta-foo2:bar2 \n
x-ms-meta-foo3:bar3\n

Notice the trailing space for foo2's value.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.