azure_file_shares 0.1.5

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8bf520f5d27bda7dba8b8f98be6697141e122bc345e65b8518f705fb5bdfaa74
4
+ data.tar.gz: 46f29aff6a2ac4735d7f78829134416103ace8e53de9858d1357d894581057d0
5
+ SHA512:
6
+ metadata.gz: dd7cea851a63d01ed0298837ecbe50433e9026c7e397ba7752fe7cd3b15f0144e7c7ffda3c63a1ea54113be69379e6990d20fb7cc5df15d794e5cf8c65427d4c
7
+ data.tar.gz: 2e52b6bda00b44a8226e7d163594113c18fb4511585edfd1afea391dad0fead13da33a1e5ec150eca010fcb8a0daee9a305d581b3f8b0e05a5cc1139c246b14a
data/Readme.md ADDED
@@ -0,0 +1,483 @@
1
+ # Azure File Shares
2
+
3
+ A Ruby gem for interacting with the Microsoft Azure File Shares API. This gem provides a simple, object-oriented interface for managing Azure File Shares and their snapshots, as well as file and directory operations.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'azure_file_shares'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ $ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```bash
22
+ $ gem install azure_file_shares
23
+ ```
24
+
25
+ ## Requirements
26
+
27
+ - Ruby 2.6 or higher
28
+ - Azure account with appropriate permissions
29
+ - Registered application in Microsoft Entra ID with API permissions
30
+
31
+ ## Usage
32
+
33
+ ### Configuration Options
34
+
35
+ The gem supports two main modes of operation, each with different configuration requirements:
36
+
37
+ ### 1. Full Access (ARM API + Storage API)
38
+
39
+ For complete functionality including share management and file operations:
40
+
41
+ ```ruby
42
+ AzureFileShares.configure do |config|
43
+ # Required for ARM operations (share management)
44
+ config.tenant_id = 'your-tenant-id'
45
+ config.client_id = 'your-client-id'
46
+ config.client_secret = 'your-client-secret'
47
+ config.subscription_id = 'your-subscription-id'
48
+ config.resource_group_name = 'your-resource-group-name'
49
+
50
+ # Required for all operations
51
+ config.storage_account_name = 'your-storage-account-name'
52
+ config.storage_account_key = 'your-storage-account-key'
53
+
54
+ # Optional settings
55
+ config.api_version = '2024-01-01' # Default
56
+ config.request_timeout = 60 # Default (in seconds)
57
+ config.logger = Logger.new(STDOUT) # Optional
58
+ end
59
+ ```
60
+
61
+ ### 2. File Operations Only (Storage API)
62
+
63
+ If you only need to work with files and directories within existing shares, you can use this simplified configuration:
64
+
65
+ ```ruby
66
+ AzureFileShares.configure do |config|
67
+ # Minimal configuration for file operations
68
+ config.storage_account_name = 'your-storage-account-name'
69
+ config.storage_account_key = 'your-storage-account-key'
70
+ end
71
+
72
+ # Now you can use file operations
73
+ client = AzureFileShares.client
74
+
75
+ # Works with existing shares
76
+ client.files.upload_file('share-name', 'path/to/directory', 'file.txt', 'file content')
77
+ client.files.list('share-name', 'path/to/directory')
78
+ client.files.download_file('share-name', 'path/to/directory', 'file.txt')
79
+
80
+ # But share management operations will fail
81
+ # client.file_shares.list # This would throw an error
82
+ ```
83
+
84
+ With the simplified configuration, you can perform all file and directory operations but cannot manage shares. Share management operations require the full configuration including resource group details.
85
+
86
+
87
+ ## Using SAS Tokens for Authentication
88
+
89
+ For file operations, you can use a Shared Access Signature (SAS) token instead of a storage account key. This is often more reliable and has less stringent permission requirements:
90
+
91
+ ```ruby
92
+ # Configure with SAS token
93
+ AzureFileShares.configure do |config|
94
+ config.storage_account_name = 'your-storage-account-name'
95
+ config.sas_token = 'sv=2020-08-04&ss=f&srt=co&sp=rwdlc&se=2025-04-30T21:00:00Z&st=2025-04-29T13:00:00Z&spr=https&sig=XXXXXXXXXXXXX'
96
+ end
97
+
98
+ # Or set it on an existing client
99
+ client = AzureFileShares.client
100
+ client.sas_token = 'sv=2020-08-04&ss=f&srt=co&sp=rwdlc&se=2025-04-30T21:00:00Z&st=2025-04-29T13:00:00Z&spr=https&sig=XXXXXXXXXXXXX'
101
+
102
+ # Then use file operations as normal
103
+ client.files.list('share-name', 'path/to/directory')
104
+ ```
105
+
106
+ ### Generating a SAS Token
107
+
108
+ You can generate a SAS token in several ways:
109
+
110
+ 1. **Azure Portal**:
111
+ - Go to your Storage Account
112
+ - Select "File shares" and choose your share
113
+ - Click "Generate SAS" in the top menu
114
+ - Configure permissions and expiry
115
+ - Click "Generate SAS token and URL"
116
+ - Copy just the token part (the query string starting with "?sv=")
117
+
118
+ 2. **Azure CLI**:
119
+ ```bash
120
+ az storage file generate-sas --account-name <storage-account> --account-key <account-key> --path <file-path> --share-name <share-name> --permissions r --expiry <expiry-date>
121
+ ```
122
+
123
+ 3. **Azure PowerShell**:
124
+ ```powershell
125
+ New-AzStorageFileSASToken -Context $ctx -ShareName <share-name> -Path <file-path> -Permission r -ExpiryTime <expiry-date>
126
+ ```
127
+
128
+ 4. **Programmatically** using this gem:
129
+ ```ruby
130
+ sas_url = AzureFileShares.client.files.generate_file_sas_url(
131
+ 'share-name',
132
+ 'path/to/directory',
133
+ 'file.txt',
134
+ expiry: Time.now + 86400, # 1 day
135
+ permissions: 'r' # Read-only
136
+ )
137
+ ```
138
+
139
+ Using SAS tokens can be more reliable than Shared Key authentication and provides more granular control over permissions and access duration.
140
+
141
+ ### Working with File Shares
142
+
143
+ #### Listing File Shares
144
+
145
+ ```ruby
146
+ # Get all file shares in the storage account
147
+ shares = AzureFileShares.client.file_shares.list
148
+
149
+ # Get shares with pagination and filtering
150
+ shares = AzureFileShares.client.file_shares.list(
151
+ maxpagesize: 10,
152
+ filter: "properties/shareQuota gt 5120"
153
+ )
154
+
155
+ # Access share properties
156
+ shares.each do |share|
157
+ puts "Share: #{share.name}"
158
+ puts " Quota: #{share.quota} GiB"
159
+ puts " Access Tier: #{share.access_tier}"
160
+ puts " Last Modified: #{share.last_modified_time}"
161
+ end
162
+ ```
163
+
164
+ #### Getting a Specific Share
165
+
166
+ ```ruby
167
+ # Get a specific file share by name
168
+ share = AzureFileShares.client.file_shares.get('my-share-name')
169
+ ```
170
+
171
+ #### Creating a New Share
172
+
173
+ ```ruby
174
+ # Create a new file share with default settings
175
+ share = AzureFileShares.client.file_shares.create('new-share-name')
176
+
177
+ # Create a new file share with specific settings
178
+ share = AzureFileShares.client.file_shares.create(
179
+ 'new-share-name',
180
+ {
181
+ shareQuota: 5120, # 5 TB quota
182
+ accessTier: 'Hot',
183
+ enabledProtocols: 'SMB'
184
+ }
185
+ )
186
+ ```
187
+
188
+ #### Updating a Share
189
+
190
+ ```ruby
191
+ # Update an existing file share
192
+ updated_share = AzureFileShares.client.file_shares.update(
193
+ 'my-share-name',
194
+ {
195
+ shareQuota: 10240, # 10 TB quota
196
+ accessTier: 'Cool'
197
+ }
198
+ )
199
+ ```
200
+
201
+ #### Deleting a Share
202
+
203
+ ```ruby
204
+ # Delete a file share
205
+ AzureFileShares.client.file_shares.delete('my-share-name')
206
+
207
+ # Delete a file share and its snapshots
208
+ AzureFileShares.client.file_shares.delete('my-share-name', delete_snapshots: true)
209
+ ```
210
+
211
+ ### Working with Snapshots
212
+
213
+ #### Creating a Snapshot
214
+
215
+ ```ruby
216
+ # Create a snapshot of a file share
217
+ snapshot = AzureFileShares.client.snapshots.create('my-share-name')
218
+
219
+ # Create a snapshot with metadata
220
+ snapshot = AzureFileShares.client.snapshots.create(
221
+ 'my-share-name',
222
+ {
223
+ 'created_by' => 'backup_service',
224
+ 'backup_id' => '12345'
225
+ }
226
+ )
227
+
228
+ # Access snapshot details
229
+ puts "Snapshot created at: #{snapshot.timestamp}"
230
+ puts "Creation time: #{snapshot.creation_time}"
231
+ ```
232
+
233
+ #### Listing Snapshots for a Share
234
+
235
+ ```ruby
236
+ # List all snapshots for a file share
237
+ snapshots = AzureFileShares.client.snapshots.list('my-share-name')
238
+
239
+ # List snapshots with pagination
240
+ snapshots = AzureFileShares.client.snapshots.list('my-share-name', maxpagesize: 10)
241
+ ```
242
+
243
+ #### Getting a Specific Snapshot
244
+
245
+ ```ruby
246
+ # Get a specific snapshot by share name and snapshot timestamp
247
+ snapshot = AzureFileShares.client.snapshots.get(
248
+ 'my-share-name',
249
+ '2023-04-01T12:00:00.0000000Z'
250
+ )
251
+ ```
252
+
253
+ #### Deleting a Snapshot
254
+
255
+ ```ruby
256
+ # Delete a specific snapshot
257
+ AzureFileShares.client.snapshots.delete(
258
+ 'my-share-name',
259
+ '2023-04-01T12:00:00.0000000Z'
260
+ )
261
+ ```
262
+
263
+ ## Working with Files and Directories
264
+
265
+ Before using file operations, make sure to set up your storage account key:
266
+
267
+ ```ruby
268
+ # Configure with storage account key
269
+ AzureFileShares.configure do |config|
270
+ # Basic configuration as above
271
+ config.storage_account_key = 'your-storage-account-key'
272
+ end
273
+
274
+ # Or set it on an existing client
275
+ AzureFileShares.client.storage_account_key = 'your-storage-account-key'
276
+ ```
277
+
278
+ ### Directory Operations
279
+
280
+ ```ruby
281
+ # Create a directory
282
+ AzureFileShares.client.files.create_directory('my-share-name', 'path/to/directory')
283
+
284
+ # Check if a directory exists
285
+ if AzureFileShares.client.files.directory_exists?('my-share-name', 'path/to/directory')
286
+ puts "Directory exists"
287
+ end
288
+
289
+ # List files and directories
290
+ contents = AzureFileShares.client.files.list('my-share-name', 'path/to/directory')
291
+
292
+ # Access directories
293
+ contents[:directories].each do |dir|
294
+ puts "Directory: #{dir[:name]}"
295
+ puts " Last Modified: #{dir[:properties][:last_modified]}"
296
+ end
297
+
298
+ # Access files
299
+ contents[:files].each do |file|
300
+ puts "File: #{file[:name]}"
301
+ puts " Size: #{file[:properties][:content_length]} bytes"
302
+ puts " Type: #{file[:properties][:content_type]}"
303
+ end
304
+
305
+ # Delete a directory (use recursive: true to delete contents)
306
+ AzureFileShares.client.files.delete_directory('my-share-name', 'path/to/directory', recursive: true)
307
+ ```
308
+
309
+ ### File Operations
310
+
311
+ #### Uploading Files
312
+
313
+ ```ruby
314
+ # Upload a file from a string
315
+ content = "This is the content of my file"
316
+ AzureFileShares.client.files.upload_file(
317
+ 'my-share-name', # Share name
318
+ 'path/to/directory', # Directory path (use '' for root)
319
+ 'myfile.txt', # File name
320
+ content, # File content
321
+ content_type: 'text/plain' # Optional content type
322
+ )
323
+
324
+ # Upload a file from disk
325
+ content = File.read('local/path/to/myfile.txt')
326
+ AzureFileShares.client.files.upload_file(
327
+ 'my-share-name',
328
+ 'path/to/directory',
329
+ 'myfile.txt',
330
+ content
331
+ )
332
+
333
+ # Upload with metadata
334
+ AzureFileShares.client.files.upload_file(
335
+ 'my-share-name',
336
+ 'path/to/directory',
337
+ 'myfile.txt',
338
+ content,
339
+ metadata: {
340
+ 'created_by' => 'user123',
341
+ 'department' => 'engineering'
342
+ }
343
+ )
344
+ ```
345
+
346
+ #### Downloading Files
347
+
348
+ ```ruby
349
+ # Download a file
350
+ content = AzureFileShares.client.files.download_file(
351
+ 'my-share-name',
352
+ 'path/to/directory',
353
+ 'myfile.txt'
354
+ )
355
+
356
+ # Save to disk
357
+ File.write('local/path/to/downloaded.txt', content)
358
+
359
+ # Download a range of bytes
360
+ partial_content = AzureFileShares.client.files.download_file(
361
+ 'my-share-name',
362
+ 'path/to/directory',
363
+ 'myfile.txt',
364
+ range: 0..1023 # First 1KB
365
+ )
366
+ ```
367
+
368
+ #### File Management
369
+
370
+ ```ruby
371
+ # Check if a file exists
372
+ if AzureFileShares.client.files.file_exists?('my-share-name', 'path/to/directory', 'myfile.txt')
373
+ puts "File exists"
374
+ end
375
+
376
+ # Get file properties
377
+ properties = AzureFileShares.client.files.get_file_properties(
378
+ 'my-share-name',
379
+ 'path/to/directory',
380
+ 'myfile.txt'
381
+ )
382
+
383
+ puts "File size: #{properties[:content_length]} bytes"
384
+ puts "Content type: #{properties[:content_type]}"
385
+ puts "Last modified: #{properties[:last_modified]}"
386
+ puts "Metadata: #{properties[:metadata]}"
387
+
388
+ # Delete a file
389
+ AzureFileShares.client.files.delete_file(
390
+ 'my-share-name',
391
+ 'path/to/directory',
392
+ 'myfile.txt'
393
+ )
394
+
395
+ # Copy a file
396
+ AzureFileShares.client.files.copy_file(
397
+ 'source-share', # Source share name
398
+ 'source/directory', # Source directory path
399
+ 'source-file.txt', # Source file name
400
+ 'dest-share', # Destination share name
401
+ 'dest/directory', # Destination directory path
402
+ 'dest-file.txt' # Destination file name
403
+ )
404
+
405
+ # Generate a SAS URL for a file (time-limited access)
406
+ sas_url = AzureFileShares.client.files.generate_file_sas_url(
407
+ 'my-share-name',
408
+ 'path/to/directory',
409
+ 'myfile.txt',
410
+ expiry: Time.now + 3600, # 1 hour from now
411
+ permissions: 'r' # Read-only access
412
+ )
413
+
414
+ puts "Access file at: #{sas_url}"
415
+ ```
416
+
417
+ ## Error Handling
418
+
419
+ The gem uses custom error classes to provide meaningful error information:
420
+
421
+ ```ruby
422
+ begin
423
+ share = AzureFileShares.client.file_shares.get('non-existent-share')
424
+ rescue AzureFileShares::Errors::ApiError => e
425
+ puts "API Error (#{e.status}): #{e.message}"
426
+ puts "Response: #{e.response}"
427
+ end
428
+
429
+ begin
430
+ AzureFileShares.configure do |config|
431
+ # Missing required fields
432
+ end
433
+ AzureFileShares.client.file_shares.list
434
+ rescue AzureFileShares::Errors::ConfigurationError => e
435
+ puts "Configuration Error: #{e.message}"
436
+ end
437
+ ```
438
+
439
+ ## Creating Microsoft Entra App Registration
440
+
441
+ Before using this gem, you need to register an application in Microsoft Entra ID:
442
+
443
+ 1. Sign in to the [Azure portal](https://portal.azure.com)
444
+ 2. Navigate to **Microsoft Entra ID** > **App registrations** > **New registration**
445
+ 3. Enter a name for your application
446
+ 4. Select the appropriate supported account type
447
+ 5. Click **Register**
448
+ 6. Once registered, note the **Application (client) ID** and **Directory (tenant) ID**
449
+ 7. Navigate to **Certificates & secrets** > **Client secrets** > **New client secret**
450
+ 8. Create a new secret and note the value (you won't be able to see it again)
451
+ 9. Navigate to **API permissions** and add the following permissions:
452
+ - Microsoft.Storage > user_impersonation
453
+ 10. Click **Grant admin consent** for your directory
454
+
455
+ You also need to assign appropriate RBAC roles to the registered application for your storage account:
456
+ - **Storage File Data SMB Share Contributor** (for full access)
457
+ - **Storage File Data SMB Share Reader** (for read access)
458
+
459
+ ## Development
460
+
461
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
462
+
463
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
464
+
465
+ ## Testing
466
+
467
+ To run the test suite:
468
+
469
+ ```bash
470
+ $ bundle exec rspec
471
+ ```
472
+
473
+ ## Contributing
474
+
475
+ 1. Fork it
476
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
477
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
478
+ 4. Push to the branch (`git push origin my-new-feature`)
479
+ 5. Create a new Pull Request
480
+
481
+ ## License
482
+
483
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,92 @@
1
+ module AzureFileShares
2
+ module Auth
3
+ # Handles authentication with Azure AD to retrieve access tokens
4
+ class TokenProvider
5
+ # Azure AD authentication endpoint
6
+ TOKEN_ENDPOINT = "https://login.microsoftonline.com/%s/oauth2/v2.0/token"
7
+ # Default token expiry buffer in seconds (5 minutes)
8
+ TOKEN_EXPIRY_BUFFER = 300
9
+ # Default resource scope
10
+ DEFAULT_SCOPE = "https://management.azure.com/.default"
11
+
12
+ attr_reader :tenant_id, :client_id, :client_secret, :scope
13
+
14
+ # Initialize a new TokenProvider
15
+ # @param [String] tenant_id Azure tenant ID
16
+ # @param [String] client_id Azure client ID (application ID)
17
+ # @param [String] client_secret Azure client secret
18
+ # @param [String] scope Resource scope, defaults to management.azure.com
19
+ def initialize(tenant_id, client_id, client_secret, scope = DEFAULT_SCOPE)
20
+ @tenant_id = tenant_id
21
+ @client_id = client_id
22
+ @client_secret = client_secret
23
+ @scope = scope
24
+ @token = nil
25
+ @token_expires_at = nil
26
+ end
27
+
28
+ # Get a valid access token, refreshing if necessary
29
+ # @return [String] Access token
30
+ def access_token
31
+ refresh_token if token_expired?
32
+ @token
33
+ end
34
+
35
+ private
36
+
37
+ # Check if the current token is expired or will expire soon
38
+ # @return [Boolean] true if token needs refresh
39
+ def token_expired?
40
+ return true if @token.nil? || @token_expires_at.nil?
41
+
42
+ Time.now.to_i >= (@token_expires_at - TOKEN_EXPIRY_BUFFER)
43
+ end
44
+
45
+ # Refresh the access token
46
+ # @return [String] New access token
47
+ def refresh_token
48
+ endpoint = format(TOKEN_ENDPOINT, tenant_id)
49
+
50
+ response = Faraday.new(url: endpoint) do |conn|
51
+ conn.request :url_encoded
52
+ conn.adapter Faraday.default_adapter
53
+ end.post do |req|
54
+ req.body = {
55
+ client_id: client_id,
56
+ client_secret: client_secret,
57
+ grant_type: "client_credentials",
58
+ scope: scope,
59
+ }
60
+ end
61
+
62
+ handle_token_response(response)
63
+ end
64
+
65
+ # Handle the token response from Azure AD
66
+ # @param [Faraday::Response] response The HTTP response
67
+ # @return [String] Access token
68
+ # @raise [AzureFileShares::Errors::ApiError] If token request fails
69
+ def handle_token_response(response)
70
+ if response.status != 200
71
+ raise AzureFileShares::Errors::ApiError.new(
72
+ "Failed to obtain access token: #{response.body}",
73
+ response.status,
74
+ response.body
75
+ )
76
+ end
77
+
78
+ data = JSON.parse(response.body)
79
+ @token = data["access_token"]
80
+ # Subtract a small buffer from expiry time to ensure token validity
81
+ @token_expires_at = Time.now.to_i + data["expires_in"].to_i
82
+ @token
83
+ rescue JSON::ParserError => e
84
+ raise AzureFileShares::Errors::ApiError.new(
85
+ "Failed to parse token response: #{e.message}",
86
+ response.status,
87
+ response.body
88
+ )
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,99 @@
1
+ module AzureFileShares
2
+ # Client for interacting with the Azure File Shares API
3
+ class Client
4
+ attr_reader :configuration
5
+
6
+ # Delegate configuration methods to configuration object
7
+ %i[
8
+ tenant_id client_id client_secret subscription_id
9
+ resource_group_name storage_account_name api_version
10
+ base_url request_timeout logger
11
+ ].each do |method|
12
+ define_method(method) do
13
+ configuration.send(method)
14
+ end
15
+ end
16
+
17
+ # Initialize a new client
18
+ # @param [AzureFileShares::Configuration] configuration Client configuration
19
+ def initialize(configuration = nil)
20
+ @configuration = configuration || AzureFileShares.configuration
21
+ @configuration.validate!
22
+ @token_provider = if @configuration.tenant_id && @configuration.client_id && @configuration.client_secret
23
+ Auth::TokenProvider.new(
24
+ @configuration.tenant_id,
25
+ @configuration.client_id,
26
+ @configuration.client_secret
27
+ )
28
+ end
29
+ @connection = nil
30
+ @operations = {}
31
+ end
32
+
33
+ # Storage account key from configuration
34
+ # @return [String] Storage account key
35
+ def storage_account_key
36
+ @configuration.storage_account_key
37
+ end
38
+
39
+ # Get the HTTP connection
40
+ # @return [Faraday::Connection] Faraday connection
41
+ def connection
42
+ @connection ||= create_connection
43
+ end
44
+
45
+ # Get an access token for authentication
46
+ # @return [String] Access token
47
+ # @raise [AzureFileShares::Errors::ConfigurationError] if OAuth credentials are missing
48
+ def access_token
49
+ if @token_provider.nil?
50
+ raise AzureFileShares::Errors::ConfigurationError,
51
+ "OAuth credentials (tenant_id, client_id, client_secret) are required for ARM operations"
52
+ end
53
+ @token_provider.access_token
54
+ end
55
+
56
+ # Get a FileSharesOperations instance
57
+ # @return [AzureFileShares::Operations::FileSharesOperations]
58
+ def file_shares
59
+ @operations[:file_shares] ||= Operations::FileSharesOperations.new(self)
60
+ end
61
+
62
+ # Get a SnapshotsOperations instance
63
+ # @return [AzureFileShares::Operations::SnapshotsOperations]
64
+ def snapshots
65
+ @operations[:snapshots] ||= Operations::SnapshotsOperations.new(self)
66
+ end
67
+
68
+ # Get a FileOperations instance
69
+ # @return [AzureFileShares::Operations::FileOperations]
70
+ def files
71
+ @operations[:files] ||= Operations::FileOperations.new(self)
72
+ end
73
+
74
+ private
75
+
76
+ # Create a new Faraday connection
77
+ # @return [Faraday::Connection] Faraday connection
78
+ def create_connection
79
+ Faraday.new do |conn|
80
+ conn.options.timeout = configuration.request_timeout
81
+ conn.request :json
82
+ conn.response :json, content_type: /\bjson$/
83
+ conn.response :logger, configuration.logger if configuration.logger
84
+ conn.request :retry, {
85
+ max: 3,
86
+ interval: 0.5,
87
+ interval_randomness: 0.5,
88
+ backoff_factor: 2,
89
+ exceptions: [
90
+ Faraday::ConnectionFailed,
91
+ Faraday::TimeoutError,
92
+ Faraday::SSLError,
93
+ ],
94
+ }
95
+ conn.adapter Faraday.default_adapter
96
+ end
97
+ end
98
+ end
99
+ end