fog-aliyun 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +114 -0
  5. data/README.md +61 -3
  6. data/fog-aliyun.gemspec +9 -4
  7. data/lib/fog/aliyun/compute.rb +34 -10
  8. data/lib/fog/aliyun/models/compute/flavor.rb +28 -0
  9. data/lib/fog/aliyun/models/compute/flavors.rb +13 -0
  10. data/lib/fog/aliyun/models/compute/image.rb +3 -1
  11. data/lib/fog/aliyun/models/compute/server.rb +13 -1
  12. data/lib/fog/aliyun/models/compute/servers.rb +2 -1
  13. data/lib/fog/aliyun/models/compute/vpcs.rb +1 -1
  14. data/lib/fog/aliyun/models/storage/directories.rb +34 -17
  15. data/lib/fog/aliyun/models/storage/directory.rb +102 -14
  16. data/lib/fog/aliyun/models/storage/file.rb +130 -120
  17. data/lib/fog/aliyun/models/storage/files.rb +69 -128
  18. data/lib/fog/aliyun/requests/compute/allocate_eip_address.rb +1 -1
  19. data/lib/fog/aliyun/requests/compute/allocate_public_ip_address.rb +1 -1
  20. data/lib/fog/aliyun/requests/compute/associate_eip_address.rb +1 -1
  21. data/lib/fog/aliyun/requests/compute/attach_disk.rb +4 -2
  22. data/lib/fog/aliyun/requests/compute/create_disk.rb +2 -2
  23. data/lib/fog/aliyun/requests/compute/create_image.rb +1 -1
  24. data/lib/fog/aliyun/requests/compute/create_security_group.rb +1 -1
  25. data/lib/fog/aliyun/requests/compute/create_security_group_egress_ip_rule.rb +5 -3
  26. data/lib/fog/aliyun/requests/compute/create_security_group_egress_sg_rule.rb +4 -2
  27. data/lib/fog/aliyun/requests/compute/create_security_group_ip_rule.rb +5 -3
  28. data/lib/fog/aliyun/requests/compute/create_security_group_sg_rule.rb +4 -2
  29. data/lib/fog/aliyun/requests/compute/create_server.rb +23 -12
  30. data/lib/fog/aliyun/requests/compute/create_snapshot.rb +1 -1
  31. data/lib/fog/aliyun/requests/compute/create_vpc.rb +4 -2
  32. data/lib/fog/aliyun/requests/compute/create_vswitch.rb +4 -2
  33. data/lib/fog/aliyun/requests/compute/delete_disk.rb +1 -1
  34. data/lib/fog/aliyun/requests/compute/delete_image.rb +1 -1
  35. data/lib/fog/aliyun/requests/compute/delete_security_group.rb +1 -1
  36. data/lib/fog/aliyun/requests/compute/delete_security_group_egress_ip_rule.rb +5 -3
  37. data/lib/fog/aliyun/requests/compute/delete_security_group_egress_sg_rule.rb +4 -2
  38. data/lib/fog/aliyun/requests/compute/delete_security_group_ip_rule.rb +5 -3
  39. data/lib/fog/aliyun/requests/compute/delete_security_group_sg_rule.rb +4 -2
  40. data/lib/fog/aliyun/requests/compute/delete_server.rb +1 -1
  41. data/lib/fog/aliyun/requests/compute/delete_snapshot.rb +1 -1
  42. data/lib/fog/aliyun/requests/compute/delete_vpc.rb +1 -1
  43. data/lib/fog/aliyun/requests/compute/delete_vswitch.rb +1 -1
  44. data/lib/fog/aliyun/requests/compute/detach_disk.rb +4 -2
  45. data/lib/fog/aliyun/requests/compute/join_security_group.rb +1 -1
  46. data/lib/fog/aliyun/requests/compute/leave_security_group.rb +1 -1
  47. data/lib/fog/aliyun/requests/compute/list_disks.rb +1 -1
  48. data/lib/fog/aliyun/requests/compute/list_eip_addresses.rb +1 -1
  49. data/lib/fog/aliyun/requests/compute/list_images.rb +1 -1
  50. data/lib/fog/aliyun/requests/compute/list_route_tables.rb +1 -1
  51. data/lib/fog/aliyun/requests/compute/list_security_group_rules.rb +1 -1
  52. data/lib/fog/aliyun/requests/compute/list_security_groups.rb +1 -1
  53. data/lib/fog/aliyun/requests/compute/list_server_types.rb +3 -3
  54. data/lib/fog/aliyun/requests/compute/list_servers.rb +10 -10
  55. data/lib/fog/aliyun/requests/compute/list_snapshots.rb +1 -1
  56. data/lib/fog/aliyun/requests/compute/list_vpcs.rb +1 -1
  57. data/lib/fog/aliyun/requests/compute/list_vrouters.rb +1 -1
  58. data/lib/fog/aliyun/requests/compute/list_vswitchs.rb +1 -1
  59. data/lib/fog/aliyun/requests/compute/list_zones.rb +1 -1
  60. data/lib/fog/aliyun/requests/compute/modify_vpc.rb +4 -2
  61. data/lib/fog/aliyun/requests/compute/modify_vswitch.rb +4 -2
  62. data/lib/fog/aliyun/requests/compute/reboot_server.rb +1 -1
  63. data/lib/fog/aliyun/requests/compute/release_eip_address.rb +1 -1
  64. data/lib/fog/aliyun/requests/compute/start_server.rb +1 -1
  65. data/lib/fog/aliyun/requests/compute/stop_server.rb +1 -1
  66. data/lib/fog/aliyun/requests/compute/unassociate_eip_address.rb +1 -1
  67. data/lib/fog/aliyun/requests/storage/abort_multipart_upload.rb +22 -0
  68. data/lib/fog/aliyun/requests/storage/complete_multipart_upload.rb +21 -0
  69. data/lib/fog/aliyun/requests/storage/copy_object.rb +16 -23
  70. data/lib/fog/aliyun/requests/storage/delete_bucket.rb +5 -14
  71. data/lib/fog/aliyun/requests/storage/delete_multiple_objects.rb +20 -0
  72. data/lib/fog/aliyun/requests/storage/delete_object.rb +12 -35
  73. data/lib/fog/aliyun/requests/storage/get_bucket.rb +30 -110
  74. data/lib/fog/aliyun/requests/storage/get_bucket_location.rb +33 -0
  75. data/lib/fog/aliyun/requests/storage/get_object.rb +29 -24
  76. data/lib/fog/aliyun/requests/storage/get_object_acl.rb +30 -0
  77. data/lib/fog/aliyun/requests/storage/get_object_http_url.rb +14 -15
  78. data/lib/fog/aliyun/requests/storage/get_object_https_url.rb +14 -15
  79. data/lib/fog/aliyun/requests/storage/get_service.rb +13 -0
  80. data/lib/fog/aliyun/requests/storage/head_object.rb +27 -18
  81. data/lib/fog/aliyun/requests/storage/initiate_multipart_upload.rb +19 -0
  82. data/lib/fog/aliyun/requests/storage/list_buckets.rb +8 -26
  83. data/lib/fog/aliyun/requests/storage/list_objects.rb +14 -73
  84. data/lib/fog/aliyun/requests/storage/put_bucket.rb +4 -10
  85. data/lib/fog/aliyun/requests/storage/put_object.rb +18 -162
  86. data/lib/fog/aliyun/requests/storage/upload_part.rb +24 -0
  87. data/lib/fog/aliyun/storage.rb +57 -29
  88. data/lib/fog/aliyun/version.rb +1 -1
  89. data/lib/fog/aliyun.rb +6 -9
  90. data/lib/fog/bin/aliyun.rb +1 -1
  91. metadata +118 -47
  92. data/lib/fog/aliyun/requests/storage/delete_container.rb +0 -33
  93. data/lib/fog/aliyun/requests/storage/get_container.rb +0 -56
  94. data/lib/fog/aliyun/requests/storage/get_containers.rb +0 -60
  95. data/lib/fog/aliyun/requests/storage/put_container.rb +0 -32
@@ -4,46 +4,134 @@ require 'fog/core/model'
4
4
  require 'fog/aliyun/models/storage/files'
5
5
 
6
6
  module Fog
7
- module Storage
8
- class Aliyun
7
+ module Aliyun
8
+ class Storage
9
9
  class Directory < Fog::Model
10
- identity :key
10
+ VALID_ACLS = ['private', 'public-read', 'public-read-write']
11
+
12
+ attr_reader :acl
13
+ identity :key, :aliases => ['Key', 'Name', 'name']
14
+
15
+ attribute :creation_date, :aliases => 'CreationDate', :type => 'time'
16
+
17
+ def acl=(new_acl)
18
+ unless VALID_ACLS.include?(new_acl)
19
+ raise ArgumentError.new("acl must be one of [#{VALID_ACLS.join(', ')}]")
20
+ else
21
+ @acl = new_acl
22
+ end
23
+ end
11
24
 
12
25
  def destroy
13
26
  requires :key
14
- prefix = key + '/'
15
- ret = service.list_objects(prefix: prefix)['Contents']
16
-
17
- if ret.nil?
18
- puts ' Not found: Direction not exist!'
27
+ service.delete_bucket(key)
28
+ true
29
+ rescue AliyunOssSdk::ServerError => error
30
+ if error.error_code == "NoSuchBucket"
19
31
  false
20
- elsif ret.size == 1
21
- service.delete_container(key)
22
- true
23
32
  else
24
- raise Fog::Storage::Aliyun::Error, ' Forbidden: Direction not empty!'
33
+ raise(error)
34
+ end
35
+ end
36
+
37
+ def destroy!(options = {})
38
+ requires :key
39
+ options = {
40
+ timeout: Fog.timeout,
41
+ interval: Fog.interval,
42
+ }.merge(options)
43
+
44
+ begin
45
+ clear!
46
+ Fog.wait_for(options[:timeout], options[:interval]) { objects_keys.size == 0 }
47
+ service.delete_bucket(key)
48
+ true
49
+ rescue AliyunOssSdk::ServerError
25
50
  false
26
51
  end
27
52
  end
28
53
 
54
+ def location
55
+ region = @aliyun_region_id
56
+ region ||= Storage::DEFAULT_REGION
57
+ @location = (bucket_location || 'oss-' + region)
58
+ end
59
+
60
+ # NOTE: you can't change the region once the bucket is created
61
+ def location=(new_location)
62
+ new_location = 'oss-' + new_location unless new_location.start_with?('oss-')
63
+ @location = new_location
64
+ end
65
+
29
66
  def files
30
67
  @files ||= begin
31
- Fog::Storage::Aliyun::Files.new(
68
+ Fog::Aliyun::Storage::Files.new(
32
69
  directory: self,
33
70
  service: service
34
71
  )
35
72
  end
36
73
  end
37
74
 
75
+ # TODO
76
+ def public=(new_public)
77
+ nil
78
+ end
79
+
80
+ # TODO
38
81
  def public_url
39
82
  nil
40
83
  end
41
84
 
42
85
  def save
43
86
  requires :key
44
- service.put_container(key)
87
+
88
+ options = {}
89
+
90
+ options['x-oss-acl'] = acl if acl
91
+
92
+ # https://help.aliyun.com/document_detail/31959.html
93
+ # if !persisted?
94
+ # # There is a sdk bug that location can not be set
95
+ # options[:location] = location
96
+ # end
97
+
98
+ service.put_bucket(key, options)
99
+ attributes[:is_persisted] = true
100
+
45
101
  true
46
102
  end
103
+
104
+ def persisted?
105
+ # is_persisted is true in case of directories.get or after #save
106
+ # creation_date is set in case of directories.all
107
+ attributes[:is_persisted] || !!attributes[:creation_date]
108
+ end
109
+
110
+ private
111
+
112
+ def bucket_location
113
+ requires :key
114
+ return nil unless persisted?
115
+ service.get_bucket_location(key)
116
+ end
117
+
118
+ def objects_keys
119
+ requires :key
120
+ bucket_query = service.get_bucket(key)
121
+
122
+ object_keys = []
123
+ i = 0
124
+ bucket_query[0].each do |o|
125
+ object_keys[i] = o.key
126
+ i += 1
127
+ end
128
+ object_keys
129
+ end
130
+
131
+ def clear!
132
+ requires :key
133
+ service.delete_multiple_objects(key, objects_keys) if objects_keys.size > 0
134
+ end
47
135
  end
48
136
  end
49
137
  end
@@ -3,78 +3,127 @@
3
3
  require 'fog/core/model'
4
4
 
5
5
  module Fog
6
- module Storage
7
- class Aliyun
6
+ module Aliyun
7
+ class Storage
8
8
  class File < Fog::Model
9
- identity :key, aliases: 'name'
9
+ identity :key, aliases: ['Key', 'Name', 'name']
10
+
11
+ attr_writer :body
12
+ attribute :cache_control, aliases: 'Cache-Control'
13
+ attribute :content_encoding, aliases: 'Content-Encoding'
10
14
  attribute :date, aliases: 'Date'
11
- attribute :content_length, aliases: 'Content-Length', type: :integer
15
+ attribute :content_length, aliases: ['Content-Length', 'Size'], type: :integer
16
+ attribute :content_md5, aliases: 'Content-MD5'
12
17
  attribute :content_type, aliases: 'Content-Type'
13
18
  attribute :connection, aliases: 'Connection'
14
19
  attribute :content_disposition, aliases: 'Content-Disposition'
15
- attribute :etag, aliases: 'Etag'
20
+ attribute :etag, aliases: ['Etag', 'ETag']
21
+ attribute :expires, aliases: 'Expires'
22
+ attribute :metadata
23
+ attribute :owner, aliases: 'Owner'
16
24
  attribute :last_modified, aliases: 'Last-Modified', type: :time
17
25
  attribute :accept_ranges, aliases: 'Accept-Ranges'
18
26
  attribute :server, aliases: 'Server'
19
- attribute :object_type, aliases: 'x-oss-object-type'
27
+ attribute :object_type, aliases: ['x-oss-object-type', 'x_oss_object_type']
28
+
29
+ # @note Chunk size to use for multipart uploads.
30
+ # Use small chunk sizes to minimize memory. E.g. 5242880 = 5mb
31
+ attr_reader :multipart_chunk_size
32
+ def multipart_chunk_size=(mp_chunk_size)
33
+ raise ArgumentError.new("minimum multipart_chunk_size is 5242880") if mp_chunk_size < 5242880
34
+ @multipart_chunk_size = mp_chunk_size
35
+ end
36
+
37
+ def acl
38
+ requires :directory, :key
39
+ service.get_object_acl(directory.key, key)
40
+ end
41
+
42
+ def acl=(new_acl)
43
+ valid_acls = ['private', 'public-read', 'public-read-write', 'default']
44
+ unless valid_acls.include?(new_acl)
45
+ raise ArgumentError.new("acl must be one of [#{valid_acls.join(', ')}]")
46
+ end
47
+ @acl = new_acl
48
+ end
20
49
 
21
50
  def body
22
- attributes[:body] ||=
23
- if last_modified
24
- collection.get(identity).body
25
- else
26
- ''
27
- end
51
+ return attributes[:body] if attributes[:body]
52
+ return '' unless last_modified
53
+
54
+ file = collection.get(identity)
55
+ if file
56
+ attributes[:body] = file.body
57
+ else
58
+ attributes[:body] = ''
59
+ end
28
60
  end
29
61
 
30
62
  def body=(new_body)
31
63
  attributes[:body] = new_body
32
64
  end
33
65
 
34
- attr_reader :directory
66
+ def directory
67
+ @directory
68
+ end
35
69
 
70
+ # Copy object from one bucket to other bucket.
71
+ #
72
+ # required attributes: directory, key
73
+ #
74
+ # @param target_directory_key [String]
75
+ # @param target_file_key [String]
76
+ # @param options [Hash] options for copy_object method
77
+ # @return [String] Fog::Aliyun::Files#head status of directory contents
78
+ #
36
79
  def copy(target_directory_key, target_file_key, options = {})
37
80
  requires :directory, :key
38
- source_object = if directory.key == ''
39
- key
40
- else
41
- directory.key + '/' + key
42
- end
43
- target_object = if target_directory_key == ''
44
- target_file_key
45
- else
46
- target_directory_key + '/' + target_file_key
47
- end
48
- service.copy_object(nil, source_object, nil, target_object, options)
49
- target_directory = service.directories.new(key: target_directory_key)
50
- target_directory.files.get(target_file_key)
51
- end
52
-
53
- def destroy
81
+ service.copy_object(directory.key, key, target_directory_key, target_file_key, options)
82
+ target_directory = service.directories.new(:key => target_directory_key)
83
+ target_directory.files.head(target_file_key)
84
+ end
85
+
86
+ def destroy(options = {})
54
87
  requires :directory, :key
55
- object = if directory.key == ''
56
- key
57
- else
58
- directory.key + '/' + key
59
- end
60
- service.delete_object(object)
88
+ # TODO support versionId
89
+ # attributes[:body] = nil if options['versionId'] == version
90
+ service.delete_object(directory.key, key, options)
61
91
  true
62
92
  end
63
93
 
94
+ remove_method :metadata
64
95
  def metadata
65
- attributes[:metadata] ||= headers_to_metadata
96
+ attributes.reject {|key, value| !(key.to_s =~ /^x-oss-/)}
97
+ end
98
+
99
+ remove_method :metadata=
100
+ def metadata=(new_metadata)
101
+ merge_attributes(new_metadata)
66
102
  end
67
103
 
104
+ remove_method :owner=
68
105
  def owner=(new_owner)
69
106
  if new_owner
70
107
  attributes[:owner] = {
71
- display_name: new_owner['DisplayName'],
72
- id: new_owner['ID']
108
+ :display_name => new_owner['DisplayName'] || new_owner[:display_name],
109
+ :id => new_owner['ID'] || new_owner[:id]
73
110
  }
74
111
  end
75
112
  end
76
113
 
114
+ # Set Access-Control-List permissions.
115
+ #
116
+ # valid new_publics: public_read, private
117
+ #
118
+ # @param [String] new_public
119
+ # @return [String] new_public
120
+ #
77
121
  def public=(new_public)
122
+ if new_public
123
+ @acl = 'public-read'
124
+ else
125
+ @acl = 'private'
126
+ end
78
127
  new_public
79
128
  end
80
129
 
@@ -83,48 +132,32 @@ module Fog
83
132
  # required attributes: directory, key
84
133
  #
85
134
  # @param expires [String] number of seconds (since 1970-01-01 00:00) before url expires
86
- # @param options [Hash]
135
+ # @param options[Hash] No need to use
87
136
  # @return [String] url
88
137
  #
89
138
  def url(expires, options = {})
90
-
91
- expires = expires.nil? ? 0 : expires.to_i
92
-
93
- requires :directory, :key
94
- object = if directory.key == ''
95
- key
96
- else
97
- directory.key + '/' + key
98
- end
99
- service.get_object_http_url_public(object, expires, options)
100
- end
101
-
102
- def public_url
103
139
  requires :key
104
- collection.get_url(key)
140
+ service.get_object_http_url_public(directory.key, key, expires)
105
141
  end
106
142
 
107
143
  def save(options = {})
108
144
  requires :body, :directory, :key
109
- options['Content-Type'] = content_type if content_type
145
+ options['x-oss-object-acl'] ||= @acl if @acl
146
+ options['Cache-Control'] = cache_control if cache_control
110
147
  options['Content-Disposition'] = content_disposition if content_disposition
111
- options.merge!(metadata_to_headers)
112
-
113
- object = if directory.key == ''
114
- key
115
- else
116
- directory.key + '/' + key
117
- end
118
- if body.is_a?(::File)
119
- data = service.put_object(object, body, options).data
120
- elsif body.is_a?(String)
121
- data = service.put_object_with_body(object, body, options).data
148
+ options['Content-Encoding'] = content_encoding if content_encoding
149
+ options['Content-MD5'] = content_md5 if content_md5
150
+ options['Content-Type'] = content_type if content_type
151
+ options['Expires'] = expires if expires
152
+ options.merge!(metadata)
153
+
154
+ self.multipart_chunk_size = 5242880 if !multipart_chunk_size && Fog::Storage.get_body_size(body) > 5368709120
155
+ if multipart_chunk_size && Fog::Storage.get_body_size(body) >= multipart_chunk_size && body.respond_to?(:read)
156
+ multipart_save(options)
122
157
  else
123
- raise Fog::Storage::Aliyun::Error, " Forbidden: Invalid body type: #{body.class}!"
158
+ service.put_object(directory.key, key, body, options)
124
159
  end
125
- update_attributes_from(data)
126
- refresh_metadata
127
-
160
+ self.etag = self.etag.gsub('"','') if self.etag
128
161
  self.content_length = Fog::Storage.get_body_size(body)
129
162
  self.content_type ||= Fog::Storage.get_content_type(body)
130
163
  true
@@ -132,66 +165,43 @@ module Fog
132
165
 
133
166
  private
134
167
 
135
- attr_writer :directory
136
-
137
- def refresh_metadata
138
- metadata.reject! { |_k, v| v.nil? }
139
- end
140
-
141
- def headers_to_metadata
142
- key_map = key_mapping
143
- Hash[metadata_attributes.map { |k, v| [key_map[k], v] }]
144
- end
145
-
146
- def key_mapping
147
- key_map = metadata_attributes
148
- key_map.each_pair { |k, _v| key_map[k] = header_to_key(k) }
168
+ def directory=(new_directory)
169
+ @directory = new_directory
149
170
  end
150
171
 
151
- def header_to_key(opt)
152
- opt.gsub(metadata_prefix, '').split('-').map { |k| k[0, 1].downcase + k[1..-1] }.join('_').to_sym
153
- end
154
-
155
- def metadata_to_headers
156
- header_map = header_mapping
157
- Hash[metadata.map { |k, v| [header_map[k], v] }]
158
- end
159
-
160
- def header_mapping
161
- header_map = metadata.dup
162
- header_map.each_pair { |k, _v| header_map[k] = key_to_header(k) }
163
- end
164
-
165
- def key_to_header(key)
166
- metadata_prefix + key.to_s.split(/[-_]/).map(&:capitalize).join('-')
167
- end
172
+ def multipart_save(options)
173
+ # Initiate the upload
174
+ upload_id = service.initiate_multipart_upload(directory.key, key, options)
168
175
 
169
- def metadata_attributes
170
- if last_modified
171
- object = if directory.key == ''
172
- key
173
- else
174
- directory.key + '/' + key
175
- end
176
+ # Store ETags of upload parts
177
+ part_tags = []
176
178
 
177
- headers = service.head_object(object).data[:headers]
178
- headers.select! { |k, _v| metadata_attribute?(k) }
179
- else
180
- {}
179
+ # Upload each part
180
+ # TODO: optionally upload chunks in parallel using threads
181
+ # (may cause network performance problems with many small chunks)
182
+ # TODO: Support large chunk sizes without reading the chunk into memory
183
+ if body.respond_to?(:rewind)
184
+ body.rewind rescue nil
185
+ end
186
+ while (chunk = body.read(multipart_chunk_size)) do
187
+ part_upload = service.upload_part(directory.key, key, upload_id, part_tags.size + 1, chunk)
188
+ part_tags << part_upload
181
189
  end
182
- end
183
190
 
184
- def metadata_attribute?(key)
185
- key.to_s =~ /^#{metadata_prefix}/
186
- end
191
+ if part_tags.empty? #it is an error to have a multipart upload with no parts
192
+ part_upload = service.upload_part(directory.key, key, upload_id, 1, '')
193
+ part_tags << part_upload
194
+ end
187
195
 
188
- def metadata_prefix
189
- 'X-Object-Meta-'
196
+ rescue
197
+ # Abort the upload & reraise
198
+ service.abort_multipart_upload(directory.key, key, upload_id) if upload_id
199
+ raise
200
+ else
201
+ # Complete the upload
202
+ service.complete_multipart_upload(directory.key, key, upload_id, part_tags)
190
203
  end
191
204
 
192
- def update_attributes_from(data)
193
- merge_attributes(data[:headers].reject { |key, _value| ['Content-Length', 'Content-Type'].include?(key) })
194
- end
195
205
  end
196
206
  end
197
207
  end