qingstor-sdk 2.2.2 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c4391cc24e2f51a70db0f75c7e99d735535b473b
4
- data.tar.gz: 463094d6e75f6494f3016947b11c93b6a4fa2797
2
+ SHA256:
3
+ metadata.gz: b4067ee8cebe68b7a6ff943ce45aab389887fec3eb3725311eb8fd0bbf44fa50
4
+ data.tar.gz: 97d3720cb4c6ac65b45fa66a1ac8b117300f96489894c19381972ed945b59c79
5
5
  SHA512:
6
- metadata.gz: 7d0e092d7899da72e282d8a72943b771d28d4ad5d36b811835b5d335deaf39c61a6f3b9d287f4d343514687bc11eb5d751ea878997064e4412841e9405a58258
7
- data.tar.gz: 1b7338a27c55f6545afc53b148e0d42c85f6555d87014649e4c76cb3f6d43041c274df1ec91bbe02355f6de1c487be5b85746d5389319e7997e21de61b48f44c
6
+ metadata.gz: 2d09dc5817005d1645473bddb4f5a354a4fef382dcf34775dee845cbffe1188d28d8684df73daf7440d58a83d6aa3e26d1c1e2199dacedf294f40fae915f25ec
7
+ data.tar.gz: a0a940c7419f8acc356977c9d336a7c982b67282624baa0e0b44e0f8b31a37cf3dd4b6e4d3f5b19ca84c6bf11a7d14c0169a522fc44d3390d297d29598be675b
data/README.md CHANGED
@@ -1,11 +1,10 @@
1
1
  # QingStor::SDK
2
2
 
3
- <span style="display: inline-block">
4
- [![Build Status](https://travis-ci.org/yunify/qingstor-sdk-ruby.svg?branch=master)](https://travis-ci.org/yunify/qingstor-sdk-ruby)
3
+ [![Build Status](https://github.com/qingstor/qingstor-sdk-ruby/workflows/Unit%20Test/badge.svg?branch=master)](https://github.com/qingstor/qingstor-sdk-ruby/actions?query=workflow%3A%22Unit+Test%22)
5
4
  [![Gem Version](https://badge.fury.io/rb/qingstor-sdk.svg)](http://badge.fury.io/rb/qingstor-sdk)
6
5
  [![API Reference](http://img.shields.io/badge/api-reference-green.svg)](https://docs.qingcloud.com/qingstor/)
7
6
  [![License](http://img.shields.io/badge/license-apache%20v2-blue.svg)](https://github.com/yunify/qingstor-sdk-ruby/blob/master/LICENSE)
8
- </span>
7
+ [![Join the chat](https://img.shields.io/badge/chat-online-blue?style=flat&logo=zulip)](https://qingstor.zulipchat.com/join/odapi42t7xhqc7v4gb2wfgjx/)
9
8
 
10
9
  The official QingStor SDK for Ruby programming language.
11
10
 
@@ -15,7 +14,9 @@ This Gem uses Ruby's _keyword arguments_ feature, thus Ruby v2.1.5 or higher is
15
14
  required. See [this article](https://robots.thoughtbot.com/ruby-2-keyword-arguments)
16
15
  for more details about _keyword arguments_.
17
16
 
18
- _Notice:_ If you are using Ruby v1.9.x, please checkout the [compatible] branch.
17
+ _Notice:_ As of sdk v2.4, we no longer support Ruby 2.4 and earlier versions since Ruby 2.4 has been marked as EOL.
18
+ If you are still using Ruby 2.4 and earlier, you may not be able to use some new features of our sdk,
19
+ so please upgrade your version of Ruby as soon as possible.
19
20
 
20
21
  ### Install from RubyGems
21
22
 
@@ -123,6 +124,11 @@ server (in case of QingStor is deployed in private environment, thus has
123
124
  different endpoint with public QingStor) either in the config file, or in
124
125
  the program dynamically.
125
126
 
127
+ **Notice:** config will not be checked available when `new`, `load` and `update`,
128
+ which will be done right before initializing `service` or `bucket`.
129
+ If you need to check config, you should call `config.check` whenever you want.
130
+ If `check` failed, a `ConfigurationError` will be raised.
131
+
126
132
  ___Code Example:___
127
133
 
128
134
  ``` ruby
@@ -132,6 +138,7 @@ require 'qingstor/sdk'
132
138
  config = QingStor::SDK::Config.new.load_default_config
133
139
 
134
140
  # Create with default value
141
+ # priority from high to low: param > env > user config > default config
135
142
  config = QingStor::SDK::Config.new({
136
143
  host: 'qingstor.dev',
137
144
  log_level: 'debug',
@@ -145,6 +152,13 @@ config = QingStor::SDK::Config.init ENV['ENV_ACCESS_KEY_ID'],
145
152
  config = QingStor::SDK::Config.new
146
153
  config = config.load_config_from_file '~/qingstor/config.yaml'
147
154
 
155
+ # Load configuration from env variable:
156
+ # QINGSTOR_ACCESS_KEY_ID for access_key_id
157
+ # QINGSTOR_SECRET_ACCESS_KEY for secret_access_key
158
+ # QINGSTOR_ENABLE_VIRTUAL_HOST_STYLE for enable_virtual_host_style
159
+ # QINGSTOR_ENDPOINT for endpoint
160
+ config = QingStor::SDK::Config.new.load_env_config
161
+
148
162
  # Change API server
149
163
  config.update({host: 'test.qingstor.com'})
150
164
  ```
@@ -160,26 +174,84 @@ secret_access_key: 'SECRET_ACCESS_KEY'
160
174
  host: 'qingstor.com'
161
175
  port: 443
162
176
  protocol: 'https'
177
+ # or set endpoint directly
178
+ # endpoint: https://qingstor.com:443
179
+
163
180
  connection_retries: 3
164
181
 
182
+ # Additional User-Agent
183
+ additional_user_agent: ""
184
+
165
185
  # Valid log levels are "debug", "info", "warn", "error", and "fatal".
166
- log_level: 'warn'
186
+ log_level: warn
187
+
188
+ # SDK will use virtual host style for all API calls if enabled
189
+ enable_virtual_host_style: false
167
190
  ```
168
191
 
169
192
  ## Change Log
170
193
  All notable changes to QingStor SDK for Ruby will be documented here.
171
194
 
195
+ ### [v2.5.0] - 2021-02-09
196
+
197
+ ### Added
198
+
199
+ - config: Add env variable support (#52)
200
+ - signer: Add support for anonymous API call (#53)
201
+ - config: Add support for endpoint (#55)
202
+ - config: Add support for enable_vhost_style (#56)
203
+
204
+ ### Changed
205
+
206
+ - Request: Modify Metadata in header to apply rfc 02 (#49)
207
+ - config: Refactor config check (#54)
208
+
209
+ ### [v2.4.0] - 2021-01-05
210
+
211
+ ### Added
212
+
213
+ - Support to set x-qs-metadata-directive header (#29)
214
+ - Add support for bucket replication (#45)
215
+
216
+ ### Changed
217
+
218
+ - ci: Transfer ci into github action (#30)
219
+
220
+ ### Fixed
221
+
222
+ - Fix metadata added into signature when empty (#44)
223
+
224
+ ### [v2.3.0] - 2020-05-11
225
+
226
+ ### Added
227
+
228
+ - Add support for lifecycle and notification.
229
+ - Add support for bucket logging, bucket cname and append object. (#24)
230
+
231
+ ### Fixed
232
+
233
+ - Modify content-type check for application/json. (#21)
234
+ - Fix sub-resources not be recognized when generate signature. (#25)
235
+ - Fix meta data not work as intended. (#26)
236
+ - Reverse fix of empty map in template, should not compact empty hash when sign. (#28)
237
+
238
+ ### [v2.2.3] - 2017-03-28
239
+
240
+ ### Fixed
241
+
242
+ - Fix status code of DELETE CORS API.
243
+
172
244
  ### [v2.2.2] - 2017-03-10
173
245
 
174
246
  ### Fixed
175
247
 
176
- - Resource is not mandatory in bucket policy statement
248
+ - Resource is not mandatory in bucket policy statement.
177
249
 
178
250
  ### [v2.2.1] - 2017-03-10
179
251
 
180
252
  #### Added
181
253
 
182
- - Allow user to append additional info to User-Agent
254
+ - Allow user to append additional info to User-Agent.
183
255
 
184
256
  ### [v2.2.0] - 2017-02-28
185
257
 
@@ -240,6 +312,10 @@ All notable changes to QingStor SDK for Ruby will be documented here.
240
312
  The Apache License (Version 2.0, January 2004).
241
313
 
242
314
  [compatible]: https://github.com/yunify/qingstor-sdk-ruby/tree/compatible
315
+ [v2.5.0]: https://github.com/yunify/qingstor-sdk-ruby/compare/v2.4.0...v2.5.0
316
+ [v2.4.0]: https://github.com/yunify/qingstor-sdk-ruby/compare/v2.3.0...v2.4.0
317
+ [v2.3.0]: https://github.com/yunify/qingstor-sdk-ruby/compare/v2.2.3...v2.3.0
318
+ [v2.2.3]: https://github.com/yunify/qingstor-sdk-ruby/compare/v2.2.2...v2.2.3
243
319
  [v2.2.2]: https://github.com/yunify/qingstor-sdk-ruby/compare/v2.2.1...v2.2.2
244
320
  [v2.2.1]: https://github.com/yunify/qingstor-sdk-ruby/compare/v2.2.0...v2.2.1
245
321
  [v2.2.0]: https://github.com/yunify/qingstor-sdk-ruby/compare/v2.1.1...v2.2.0
@@ -15,10 +15,13 @@
15
15
  # +-------------------------------------------------------------------------
16
16
 
17
17
  require 'fileutils'
18
+ require 'ipaddr'
19
+ require 'uri'
18
20
  require 'yaml'
19
21
 
20
22
  require 'active_support/core_ext/hash/keys'
21
23
  require 'active_support/core_ext/hash/deep_merge'
24
+ require 'active_support/core_ext/object/blank'
22
25
  require 'net/http/persistent'
23
26
 
24
27
  module QingStor
@@ -26,33 +29,83 @@ module QingStor
26
29
  class Config < Hash
27
30
  attr_accessor :connection
28
31
 
32
+ DEFAULT_AS_HASH = {
33
+ host: 'qingstor.com'.freeze,
34
+ port: 443,
35
+ protocol: 'https'.freeze,
36
+ connection_retries: 3,
37
+ log_level: 'warn'.freeze,
38
+ enable_virtual_host_style: false
39
+ }
40
+
29
41
  def self.init(access_key_id, secret_access_key)
30
42
  initial_config = {
31
43
  access_key_id: access_key_id,
32
- secret_access_key: secret_access_key,
44
+ secret_access_key: secret_access_key
33
45
  }
34
46
  Config.new(initial_config)
35
47
  end
36
48
 
37
49
  def initialize(initial_config = {})
38
50
  self.connection = Net::HTTP::Persistent.new
51
+ # load default config as basic
39
52
  load_default_config
53
+ # load from config, env path superior to ~/.qingstor/config.yaml
54
+ load_user_config_path_exist
55
+ # load envs, cover corresponding config if env exists
56
+ load_env_config
57
+ # cover by user's param
40
58
  update initial_config
41
59
  end
42
60
 
43
61
  def update(another_config = {})
44
62
  deep_merge! another_config.deep_symbolize_keys!
63
+ parse_boolean(:enable_virtual_host_style)
45
64
  Logger.set_level self[:log_level]
46
65
  self
47
66
  end
48
67
 
49
68
  def check
50
- [:access_key_id, :secret_access_key, :host, :port, :protocol].each do |x|
51
- if !self[x] || self[x].to_s.empty?
52
- raise ConfigurationError, "#{x.to_sym} not specified"
69
+ if self[:access_key_id].blank? && self[:secret_access_key].present? ||
70
+ self[:access_key_id].present? && self[:secret_access_key].blank?
71
+ raise ConfigurationError, 'ak and sk should be both both empty or not empty'
72
+ end
73
+
74
+ if self[:access_key_id].blank? && self[:secret_access_key].blank?
75
+ Logger.warn 'Both ak and sk not configured, will call api as anonymous user'
76
+ end
77
+
78
+ # check endpoint and host/port/protocol
79
+ if self[:endpoint].blank?
80
+ # host/port/protocol must set if endpoint not set
81
+ %i[host port protocol].each do |x|
82
+ if self[x].blank?
83
+ raise ConfigurationError, "#{x.to_sym} not specified"
84
+ end
85
+ end
86
+ else
87
+ # if endpoint set, host/port/protocol ignore, and warn
88
+ %i[host port protocol].each do |x|
89
+ if self[x].present? && !Config.is_default?(x, self[x])
90
+ Logger.warn "Endpoint configured, #{x.to_sym} will be ignored"
91
+ end
53
92
  end
54
93
  end
55
- if self[:additional_user_agent] && !self[:additional_user_agent].empty?
94
+
95
+ # add ip check for vhost enabled
96
+ if self[:enable_virtual_host_style]
97
+ if self[:endpoint].present?
98
+ uri = Preprocessor.parse_endpoint self[:endpoint]
99
+ ip = uri.host
100
+ else
101
+ ip = self[:host]
102
+ end
103
+ if is_valid_ip? ip
104
+ raise ConfigurationError, 'ip host not allowed if vhost enabled'
105
+ end
106
+ end
107
+
108
+ if self[:additional_user_agent].present?
56
109
  self[:additional_user_agent].each_byte do |x|
57
110
  # Allow space(32) to ~(126) in ASCII Table, exclude "(34).
58
111
  if x < 32 || x > 126 || x == 32 || x == 34
@@ -64,12 +117,36 @@ module QingStor
64
117
  end
65
118
 
66
119
  def load_default_config
67
- load_config_from_file Contract::DEFAULT_CONFIG_FILEPATH
120
+ update DEFAULT_AS_HASH
68
121
  end
69
122
 
70
123
  def load_user_config
71
- install_default_user_config unless File.exist? Contract::USER_CONFIG_FILEPATH
72
- load_config_from_file Contract::USER_CONFIG_FILEPATH
124
+ if !ENV[Contract::ENV_CONFIG_PATH].nil?
125
+ load_config_from_file ENV[Contract::ENV_CONFIG_PATH]
126
+ else
127
+ load_config_from_file Contract::USER_CONFIG_FILEPATH
128
+ end
129
+ end
130
+
131
+ def load_env_config
132
+ another_config = {}
133
+ unless ENV[Contract::ENV_ACCESS_KEY_ID].nil?
134
+ another_config[:access_key_id] =
135
+ ENV[Contract::ENV_ACCESS_KEY_ID]
136
+ end
137
+ unless ENV[Contract::ENV_SECRET_ACCESS_KEY].nil?
138
+ another_config[:secret_access_key] =
139
+ ENV[Contract::ENV_SECRET_ACCESS_KEY]
140
+ end
141
+ unless ENV[Contract::ENV_ENABLE_VIRTUAL_HOST_STYLE].nil?
142
+ another_config[:enable_virtual_host_style] =
143
+ ENV[Contract::ENV_ENABLE_VIRTUAL_HOST_STYLE]
144
+ end
145
+ unless ENV[Contract::ENV_ENDPOINT].nil?
146
+ another_config[:endpoint] =
147
+ ENV[Contract::ENV_ENDPOINT]
148
+ end
149
+ update another_config
73
150
  end
74
151
 
75
152
  def load_config_from_file(path)
@@ -79,10 +156,30 @@ module QingStor
79
156
 
80
157
  private
81
158
 
82
- def install_default_user_config
83
- Logger.warn "Installing default config file to #{Contract::USER_CONFIG_FILEPATH}"
84
- FileUtils.mkdir_p Contract::USER_SUPPORT_DIRECTORY
85
- FileUtils.copy Contract::DEFAULT_CONFIG_FILEPATH, Contract::USER_CONFIG_FILEPATH
159
+ # load user config if path exist, and skip check
160
+ def load_user_config_path_exist
161
+ # if env path configured, update from env; otherwise, if ~/.qingstor/config.yaml exists, update from this
162
+ if !ENV[Contract::ENV_CONFIG_PATH].nil? && File.exist?(ENV[Contract::ENV_CONFIG_PATH])
163
+ load_config_from_file ENV[Contract::ENV_CONFIG_PATH]
164
+ elsif File.exist? Contract::USER_CONFIG_FILEPATH
165
+ load_config_from_file Contract::USER_CONFIG_FILEPATH
166
+ end
167
+ end
168
+
169
+ def parse_boolean(key)
170
+ self[key.to_sym] = self[key.to_sym].to_s.downcase == 'true'
171
+ end
172
+
173
+ def is_valid_ip?(ip)
174
+ IPAddr.new ip
175
+ true
176
+ rescue
177
+ false
178
+ end
179
+
180
+ # @return boolean
181
+ def self.is_default?(key, value)
182
+ DEFAULT_AS_HASH[key.to_sym].present? && DEFAULT_AS_HASH[key.to_sym] == value
86
183
  end
87
184
  end
88
185
  end
@@ -23,9 +23,12 @@ module QingStor
23
23
 
24
24
  # GEM_DIRECTORY = Gem::Specification.find_by_name('qingcloud-sdk').gem_dir
25
25
  # DEFAULT_SUPPORT_DIRECTORY = GEM_DIRECTORY + '/lib/qingcloud/sdk/commons/default'
26
- DEFAULT_SUPPORT_DIRECTORY = File.expand_path(File.dirname(__FILE__) + '/./default')
27
- DEFAULT_CONFIG_FILENAME = 'config.yaml'.freeze
28
- DEFAULT_CONFIG_FILEPATH = "#{DEFAULT_SUPPORT_DIRECTORY}/#{DEFAULT_CONFIG_FILENAME}".freeze
26
+
27
+ ENV_ACCESS_KEY_ID = 'QINGSTOR_ACCESS_KEY_ID'.freeze
28
+ ENV_SECRET_ACCESS_KEY = 'QINGSTOR_SECRET_ACCESS_KEY'.freeze
29
+ ENV_CONFIG_PATH = 'QINGSTOR_CONFIG_PATH'.freeze
30
+ ENV_ENABLE_VIRTUAL_HOST_STYLE = 'QINGSTOR_ENABLE_VIRTUAL_HOST_STYLE'.freeze
31
+ ENV_ENDPOINT = 'QINGSTOR_ENDPOINT'.freeze
29
32
  end
30
33
  end
31
34
  end
@@ -23,7 +23,7 @@ module QingStor
23
23
  @@level = :warn
24
24
 
25
25
  def self.set_level(level)
26
- index = %w(debug info warn error fatal).find_index level.to_s
26
+ index = %w[debug info warn error fatal].find_index level.to_s
27
27
  @@logger.level = index.nil? ? 0 : index
28
28
  @@level = level.to_sym
29
29
  end
@@ -17,8 +17,10 @@
17
17
  require 'cgi'
18
18
  require 'base64'
19
19
  require 'digest'
20
+ require 'uri'
20
21
 
21
22
  require 'active_support/core_ext/hash/keys'
23
+ require 'active_support/core_ext/object/blank'
22
24
  require 'mimemagic'
23
25
 
24
26
  module QingStor
@@ -41,6 +43,15 @@ module QingStor
41
43
  def self.request_endpoint(input)
42
44
  config = input[:config]
43
45
  zone = input[:properties] ? input[:properties][:zone] : nil
46
+ # handle endpoint directly if it exists
47
+ if config[:endpoint].present?
48
+ uri = parse_endpoint(config[:endpoint].to_s)
49
+ if zone
50
+ uri.host = "#{zone}.#{uri.host}"
51
+ end
52
+ return uri.to_s
53
+ end
54
+
44
55
  if zone
45
56
  "#{config[:protocol]}://#{zone}.#{config[:host]}:#{config[:port]}"
46
57
  else
@@ -49,6 +60,16 @@ module QingStor
49
60
  end
50
61
 
51
62
  def self.request_uri(input)
63
+ config = input[:config]
64
+ # if enable vhost, and uri property contains bucket-name
65
+ if config[:enable_virtual_host_style] && input[:properties].present? && input[:properties][:"bucket-name"]
66
+ uri = parse_endpoint input[:request_endpoint]
67
+ # modify host, add bucket before
68
+ uri.host = "#{input[:properties][:"bucket-name"]}.#{uri.host}"
69
+ input[:request_endpoint] = uri.to_s
70
+ # handle request_uri, remove prefix (bucket-name)
71
+ input[:request_uri].delete_prefix! URI_BUCKET_PREFIX if input[:request_uri].start_with? URI_BUCKET_PREFIX
72
+ end
52
73
  unless input[:properties].nil?
53
74
  input[:properties].each do |k, v|
54
75
  input[:request_uri].gsub! "<#{k}>", (escape v.to_s)
@@ -102,6 +123,21 @@ module QingStor
102
123
  input[:request_headers][:'Content-MD5'] = Base64.encode64(Digest::MD5.digest(input[:request_body])).strip
103
124
  end
104
125
 
126
+ # X-QS-MetaData used to handle meta data
127
+ unless input[:request_headers][:'X-QS-MetaData'].nil?
128
+ if input[:request_headers][:'X-QS-MetaData'].is_a?(Hash) && !input[:request_headers][:'X-QS-MetaData'].empty?
129
+ prefix = 'x-qs-meta-'
130
+ input[:request_headers][:'X-QS-MetaData'].each do |k, v|
131
+ k = k.to_s
132
+ # add prefix for meta data in header
133
+ k = prefix + k
134
+ input[:request_headers][:"#{k}"] = v
135
+ end
136
+ end
137
+ # remove X-QS-MetaData from request header
138
+ input[:request_headers].delete :'X-QS-MetaData'
139
+ end
140
+
105
141
  input[:request_headers].map do |k, v|
106
142
  input[:request_headers][k] = escape v.to_s unless v.to_s.ascii_only?
107
143
  end
@@ -139,6 +175,32 @@ module QingStor
139
175
  origin.gsub! '+', '%20'
140
176
  origin
141
177
  end
178
+
179
+ # try to parse endpoint:
180
+ # if endpoint invalid, means ip host without scheme, like: 192.168.0.1:3000
181
+ # if endpoint parsed, and no scheme find, means host without scheme, like: qingstor.dev
182
+ # both above will add default schema at the start, and parse again
183
+ def self.parse_endpoint(endpoint)
184
+ if endpoint.blank?
185
+ raise 'endpoint should not be empty when parse'
186
+ end
187
+
188
+ begin
189
+ uri = URI.parse endpoint
190
+ unless uri.scheme.nil?
191
+ return uri
192
+ end
193
+ rescue URI::InvalidURIError
194
+ # Ignored and continue, add scheme prefix then
195
+ end
196
+
197
+ endpoint = "http://#{endpoint}" # add default scheme for endpoint
198
+ URI.parse endpoint
199
+ end
200
+
201
+ private
202
+
203
+ URI_BUCKET_PREFIX = '/<bucket-name>'.freeze
142
204
  end
143
205
  end
144
206
  end