ruby-atmos 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +8 -0
- data/README +129 -0
- data/Rakefile +94 -0
- data/lib/atmos/attributes.rb +545 -0
- data/lib/atmos/exceptions.rb +28 -0
- data/lib/atmos/object.rb +263 -0
- data/lib/atmos/parser.rb +110 -0
- data/lib/atmos/request.rb +188 -0
- data/lib/atmos/rest.rb +254 -0
- data/lib/atmos/store.rb +209 -0
- data/lib/atmos/util.rb +40 -0
- data/lib/atmos/version.rb +12 -0
- data/test/credentials.rb +141 -0
- data/test/esutest.rb +499 -0
- data/test/files/SmallImageForTest.iso +0 -0
- data/test/files/dragaf-tiny-from-vsphere.ova +0 -0
- data/test/files/something.txt +1 -0
- data/test/request_test.rb +50 -0
- data/test/suite.rb +9 -0
- data/test/suite_noproxy.rb +12 -0
- data/test/suite_proxy.rb +14 -0
- data/test/test_acl.rb +283 -0
- data/test/test_metadata.rb +162 -0
- data/test/test_object_create.rb +152 -0
- data/test/test_object_misc.rb +80 -0
- data/test/test_object_read.rb +114 -0
- data/test/test_object_update.rb +58 -0
- data/test/test_store.rb +125 -0
- data/test/test_util.rb +58 -0
- metadata +164 -0
data/COPYING
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# Copyright (c) 2011 EMC Corporation
|
2
|
+
# All Rights Reserved
|
3
|
+
#
|
4
|
+
# This software contains the intellectual property of EMC Corporation
|
5
|
+
# or is licensed to EMC Corporation from third parties. Use of this
|
6
|
+
# software and the intellectual property contained therein is expressly
|
7
|
+
# limited to the terms and conditions of the License Agreement under which
|
8
|
+
# it is provided by or on behalf of EMC.
|
data/README
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
#
|
2
|
+
# = Atmos
|
3
|
+
#
|
4
|
+
# This gem is a Ruby library for EMC's Atmos (http://www.emc.com/atmos) REST API.
|
5
|
+
#
|
6
|
+
# Logging is done via the <tt>Log4r</tt> package. The name of the logger is the string +atmos+,
|
7
|
+
# and the log level is set to <tt>Log4r::FATAL</tt> by default.
|
8
|
+
#
|
9
|
+
# == Getting started
|
10
|
+
#
|
11
|
+
# You'll need this gem and a URL to your Atmos installation.
|
12
|
+
# You'll also need your Atmos authentication credentials.
|
13
|
+
#
|
14
|
+
# == Basics
|
15
|
+
# === XML Parser
|
16
|
+
#
|
17
|
+
# The very first thing is that you have to pick your XML parser. There are two options:
|
18
|
+
# <tt>nokogiri</tt> for performance or <tt>rexml</tt> for only pure ruby requirements.
|
19
|
+
# Setting the parser does the require, so make sure you have the gem you want to use installed.
|
20
|
+
# This gem doesn't require either, since it doesn't know which you plan to use.
|
21
|
+
#
|
22
|
+
# Atmos::Parser::parser = Atmos::Parser::NOKOGIRI
|
23
|
+
# Atmos::Parser::parser = Atmos::Parser::REXML
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# === Datastore
|
27
|
+
#
|
28
|
+
# The Atmos installation you will be connecting to is your datastore. You
|
29
|
+
# can see more detailed information at <tt>Atmos::Store</tt>.
|
30
|
+
#
|
31
|
+
# store = Atmos::Store.new(:url => "https://mgmt.atmosonline.com",
|
32
|
+
# :uid => <your identifier>,
|
33
|
+
# :secret => <your secret>)
|
34
|
+
#
|
35
|
+
# From your store, you can create and retrieve objects. Note that objects
|
36
|
+
# are always retrieved without their data, so a progressive download can
|
37
|
+
# be done.
|
38
|
+
#
|
39
|
+
# obj = store.get(id)
|
40
|
+
# newobj = store.create(options)
|
41
|
+
#
|
42
|
+
# Now that you have a store, you can iterate through all listable tags, object ids
|
43
|
+
# by listable tag, and objects by listable tag. Remember that when an Atmos::Object
|
44
|
+
# is instantiated, it loads all ACLs and Metadata.
|
45
|
+
#
|
46
|
+
# store.each_listable_tag { |tag| }
|
47
|
+
#
|
48
|
+
# store.each_object_id_by_tag(tag) do |id|
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# store.each_object_by_tag(tag) do |obj|
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
#
|
55
|
+
# === Objects
|
56
|
+
# See <tt>Atmos::Object</tt> for more detailed info on the object API.
|
57
|
+
#
|
58
|
+
# ==== Creating Objects
|
59
|
+
#
|
60
|
+
# At the very simplest, you can create an object with nothing. The mimetype
|
61
|
+
# will default to <tt>binary/octet-stream</tt>. The object will be created
|
62
|
+
# with no data, no metadata, with permissions set for only the creator to access.
|
63
|
+
#
|
64
|
+
# obj = store.create()
|
65
|
+
#
|
66
|
+
# You can also create an object with (any combination of) more information
|
67
|
+
# up front. (See <tt>Atmos::Store.create</tt> for more detailed info on
|
68
|
+
# object creation options.)
|
69
|
+
#
|
70
|
+
# obj = store.create(
|
71
|
+
# :user_acl => {'janet' => :full, 'xavier' => :read},
|
72
|
+
# :group_acl => {'other' => :read},
|
73
|
+
# :metadata => {'key' => 'value', 'another_key' => nil},
|
74
|
+
# :listable_metadata => {'lkey' => 'lvalue', 'another_lkey' => nil},
|
75
|
+
# :mimetype => "video/quicktime",
|
76
|
+
# :data => open("local/video_file.qt")
|
77
|
+
# )
|
78
|
+
#
|
79
|
+
# ===== Reading Objects
|
80
|
+
#
|
81
|
+
# You can get the data for an object all at once, which means it all
|
82
|
+
# gets read into memory all at once:
|
83
|
+
#
|
84
|
+
# @data = @obj.data
|
85
|
+
#
|
86
|
+
# Or progressively download it by passing a block to <tt>read</tt>:
|
87
|
+
#
|
88
|
+
# @file = open('mydata', 'w')
|
89
|
+
# @obj.read do |chunk|
|
90
|
+
# @file.write chunk
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
#
|
94
|
+
# ===== Object Metadata
|
95
|
+
#
|
96
|
+
# An object has system and user metadata. User metadata can be modified,
|
97
|
+
# system metadata cannot. User metadata is further divided into listable
|
98
|
+
# and non-listable metadata. Listable metadata means the object is indexed
|
99
|
+
# on the key value of the metadata key/value pair (also known as a
|
100
|
+
# <tt>tag</tt>), and can be retrieved via the tag.
|
101
|
+
#
|
102
|
+
# Set or change user metadata, listable or non-listable, keys and values must be Strings:
|
103
|
+
#
|
104
|
+
# @obj.metadata[newkey] = "newvalue"
|
105
|
+
#
|
106
|
+
# (See <tt>Atmos::Metadata</tt> for more detailed info.)
|
107
|
+
#
|
108
|
+
#
|
109
|
+
# ===== Object ACL
|
110
|
+
#
|
111
|
+
# There are two ACLs on an object: <tt>user_acl</tt> and <tt>group_acl</tt>.
|
112
|
+
# User and group names are ruby Strings, while the values are one of the
|
113
|
+
# following symbols: <tt>:none</tt>, <tt>:read</tt>, <tt>:write</tt> ,<tt>:full</tt>.
|
114
|
+
# They are accessed as Hashes on the object. All normal Hash functions will work.
|
115
|
+
#
|
116
|
+
# When permissions are set to <tt>:none</tt> the pair disappears from the hash.
|
117
|
+
#
|
118
|
+
# @obj.user_acl['user'] = :full
|
119
|
+
# @obj.group_acl['other'] = :none
|
120
|
+
#
|
121
|
+
# (See <tt>Atmos::ACL</tt> for more detailed info.)
|
122
|
+
#
|
123
|
+
#
|
124
|
+
# ===== Other Object Functionality
|
125
|
+
#
|
126
|
+
# @obj.exists?
|
127
|
+
# @obj.delete
|
128
|
+
#
|
129
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rdoc/task'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'test/unit'
|
8
|
+
|
9
|
+
require File.dirname(__FILE__) + '/lib/atmos'
|
10
|
+
|
11
|
+
task :default => :test
|
12
|
+
|
13
|
+
|
14
|
+
desc "run tests"
|
15
|
+
Rake::TestTask.new do |t|
|
16
|
+
libdir = File.expand_path('lib')
|
17
|
+
testdir = File.expand_path('test')
|
18
|
+
|
19
|
+
t.libs = [libdir, testdir]
|
20
|
+
#t.pattern = 'test/lib/test_*.rb'
|
21
|
+
t.verbose = true
|
22
|
+
#t.warning = true
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
Rake::RDocTask.new do |rdoc|
|
27
|
+
rdoc.rdoc_dir = 'doc'
|
28
|
+
rdoc.title = "EMC Atmos' REST API gem - ruby-atmos"
|
29
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--main=README'
|
30
|
+
rdoc.rdoc_files.include('README')
|
31
|
+
rdoc.rdoc_files.include('lib/*.rb')
|
32
|
+
rdoc.rdoc_files.include('lib/atmos/*.rb')
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
spec = Gem::Specification.new do |s|
|
37
|
+
s.platform = Gem::Platform::RUBY
|
38
|
+
s.name = 'ruby-atmos'
|
39
|
+
s.version = Gem::Version.new(Atmos::VERSION)
|
40
|
+
s.summary = "Client library for EMC's Atmos REST API"
|
41
|
+
s.description = "Ruby OO library to access an EMC Atmos server as a blobstore."
|
42
|
+
s.email = 'fleur.dragan@emc.com'
|
43
|
+
s.author = 'Fleur Dragan'
|
44
|
+
s.has_rdoc = true
|
45
|
+
s.extra_rdoc_files = %w(README COPYING)
|
46
|
+
s.homepage = 'http://www.emc.com/atmos'
|
47
|
+
#s.rubyforge_project = 'atmos'
|
48
|
+
s.files = FileList['Rakefile', 'lib/*/*.rb']
|
49
|
+
s.test_files = Dir['test/**/*']
|
50
|
+
s.require_path = 'lib'
|
51
|
+
s.add_dependency('log4r', '>=1.1.9')
|
52
|
+
s.add_dependency('ruby-hmac', '>=0.4.0')
|
53
|
+
|
54
|
+
s.rdoc_options = ['--title', "EMC Atmos' REST API gem - ruby-atmos",
|
55
|
+
'--main', 'README',
|
56
|
+
'--line-numbers', '--inline-source']
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# DRY: set up gemspecs for both versions of gems
|
61
|
+
#
|
62
|
+
nokogiri_spec = spec.clone
|
63
|
+
nokogiri_spec.name = 'ruby-atmos'
|
64
|
+
nokogiri_spec.add_dependency('nokogiri', '>=1.4.4')
|
65
|
+
nokogiri_spec.description = "Ruby OO library to access an EMC Atmos server with nokogiri as XML parser."
|
66
|
+
nokogiri_spec.rubyforge_project = nokogiri_spec.name
|
67
|
+
|
68
|
+
rexml_spec = spec.clone
|
69
|
+
rexml_spec.name = 'ruby-atmos-pure'
|
70
|
+
rexml_spec.files += FileList['lib/atmos/*/rexml.rb']
|
71
|
+
rexml_spec.description = "Ruby OO library to access an EMC Atmos server with rexml as XML parser."
|
72
|
+
rexml_spec.rubyforge_project = rexml_spec.name
|
73
|
+
|
74
|
+
|
75
|
+
#
|
76
|
+
# creating the actual package tasks for the two gems
|
77
|
+
#
|
78
|
+
Rake::GemPackageTask.new(nokogiri_spec) { |pkg| }
|
79
|
+
Rake::GemPackageTask.new(rexml_spec) { |pkg| }
|
80
|
+
|
81
|
+
|
82
|
+
desc 'install gem with dependencies'
|
83
|
+
task :install => :package do
|
84
|
+
sh "sudo gem i pkg/#{spec.name}-#{spec.version}.gem"
|
85
|
+
end
|
86
|
+
|
87
|
+
desc 'uninstall gem'
|
88
|
+
task :uninstall do
|
89
|
+
sh "sudo gem uninstall #{spec.name} -x"
|
90
|
+
end
|
91
|
+
|
92
|
+
desc 'reinstall gem'
|
93
|
+
task :reinstall => [:uninstall, :install]
|
94
|
+
|
@@ -0,0 +1,545 @@
|
|
1
|
+
module Atmos
|
2
|
+
|
3
|
+
#
|
4
|
+
# == AttributeHashBase
|
5
|
+
#
|
6
|
+
# Base class for ACL and Metadata objects. Contains utility methods common to both,
|
7
|
+
# as well as implementations of standard ruby Hash functions to make both ACL and
|
8
|
+
# Metadata objects appear as hashes while interacting with Atmos at the same time.
|
9
|
+
#
|
10
|
+
#
|
11
|
+
class AttributeHashBase < Hash # :nodoc:
|
12
|
+
attr_reader :last_reload_at
|
13
|
+
|
14
|
+
@obj = nil
|
15
|
+
@header = nil
|
16
|
+
@last_reload_at = nil
|
17
|
+
@set_action = nil
|
18
|
+
@reload_action = nil
|
19
|
+
@delete_action = nil
|
20
|
+
|
21
|
+
|
22
|
+
def delete_with_atmos(key)
|
23
|
+
delete_without_atmos(key)
|
24
|
+
end
|
25
|
+
alias :delete_without_atmos :delete
|
26
|
+
alias :delete :delete_with_atmos
|
27
|
+
|
28
|
+
|
29
|
+
def clear_with_atmos
|
30
|
+
clear_without_atmos
|
31
|
+
end
|
32
|
+
alias :clear_without_atmos :clear
|
33
|
+
alias :clear :clear_with_atmos
|
34
|
+
|
35
|
+
|
36
|
+
def replace_with_atmos(h)
|
37
|
+
validate_input_hash(h)
|
38
|
+
replace_without_atmos(h)
|
39
|
+
end
|
40
|
+
alias :replace_without_atmos :replace
|
41
|
+
alias :replace :replace_with_atmos
|
42
|
+
|
43
|
+
|
44
|
+
def merge_with_atmos(h)
|
45
|
+
validate_input_hash(h)
|
46
|
+
merge_without_atmos(h)
|
47
|
+
end
|
48
|
+
alias :merge_without_atmos :merge
|
49
|
+
alias :merge :merge_with_atmos
|
50
|
+
|
51
|
+
|
52
|
+
def merge_with_atmos!(h)
|
53
|
+
validate_input_hash(h)
|
54
|
+
|
55
|
+
input = h.clone
|
56
|
+
input.each do |k,v|
|
57
|
+
input[k] = xlate_value_from_object_to_header(v)
|
58
|
+
end
|
59
|
+
|
60
|
+
response = @obj.request.do(@set_action, :id => @obj.aoid, @header => Atmos::Util.hash2header(input))
|
61
|
+
reload(@reload_action, @obj.aoid)
|
62
|
+
end
|
63
|
+
alias :merge_without_atmos! :merge!
|
64
|
+
alias :merge! :merge_with_atmos!
|
65
|
+
alias :update :merge!
|
66
|
+
|
67
|
+
|
68
|
+
def default_with_atmos=
|
69
|
+
raise Atmos::Exceptions::NotImplementedExceptions, "Not implemented for Atmos Object properties."
|
70
|
+
end
|
71
|
+
alias :default_without_atmos= :default=
|
72
|
+
alias :default= :default_with_atmos=
|
73
|
+
|
74
|
+
|
75
|
+
#
|
76
|
+
# Same as self[key] = value
|
77
|
+
#
|
78
|
+
def store(key, value)
|
79
|
+
self[key] = value
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
#
|
84
|
+
# This method is used internally to generate REST requests to the Atmos server.
|
85
|
+
#
|
86
|
+
# Each ACL or Metadata object knows what HTTP header it is representing
|
87
|
+
# in the Atmos REST API we're wrapping. This method returns that.
|
88
|
+
#
|
89
|
+
def header_name
|
90
|
+
return @header
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
#
|
95
|
+
# This method is used internally to generate REST requests to the Atmos server.
|
96
|
+
#
|
97
|
+
# Returns data as a String in the form of a valid HTTP header Atmos will
|
98
|
+
# recognize.
|
99
|
+
#
|
100
|
+
def header_value
|
101
|
+
Atmos::Util.hash2header(self)
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
#
|
106
|
+
# This method is used internally to generate REST requests to the Atmos server.
|
107
|
+
#
|
108
|
+
def to_header
|
109
|
+
"#{self.header_name}: #{self.header_value}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_canonicalized_header
|
113
|
+
self.to_header.squeeze
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
protected
|
118
|
+
def validate_input_hash(h)
|
119
|
+
true
|
120
|
+
end
|
121
|
+
|
122
|
+
def xlate_value_from_header_to_object(value)
|
123
|
+
value
|
124
|
+
end
|
125
|
+
|
126
|
+
def reload(action, aoid, options = {})
|
127
|
+
response = @obj.request.do(action, options.merge({:id => aoid}))
|
128
|
+
Atmos::LOG.debug("AttributeHashBase.reload: type: #{@type}; header: #{@header}; value: #{response[@header]}")
|
129
|
+
newval = Atmos::Util.header2hash(response[@header])
|
130
|
+
Atmos::LOG.debug("AttributeHashBase.reload: newval: #{newval.inspect}")
|
131
|
+
if (!newval.nil?)
|
132
|
+
newval.each do |key,val|
|
133
|
+
newval[key] = xlate_value_from_header_to_object(val)
|
134
|
+
end
|
135
|
+
Atmos::LOG.debug("AttributeHashBase.reload: newval: #{newval.inspect}")
|
136
|
+
replace_without_atmos(newval)
|
137
|
+
end
|
138
|
+
Atmos::LOG.debug("AttributeHashBase.reload: self: #{self.inspect}")
|
139
|
+
|
140
|
+
@last_reload_at = Time.now()
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
# == Access Control Lists (ACLs)
|
147
|
+
#
|
148
|
+
# There are two hashes for access control, available as properties on the object: +user_acl+ and +group_acl+.
|
149
|
+
# The keys are the Atmos usernames and the values are one of :+none+, :+read+, :+write+, :+full+. The ACLs
|
150
|
+
# behave like normal Hash objects. All operations are executed against the Atmos server immediately.
|
151
|
+
#
|
152
|
+
# === Defaults
|
153
|
+
# By default, when you create an object, the user you gave as a parameter when instantiating Atmos::Store
|
154
|
+
# has full permissions on the object The default group is +other+. So:
|
155
|
+
#
|
156
|
+
# puts obj.user_acl.inspect => {user => :full}
|
157
|
+
# puts obj.group_acl.inspect => {other => :none}
|
158
|
+
#
|
159
|
+
#
|
160
|
+
# === Adding
|
161
|
+
# Adding permissions for a new user is as easy as adding another hash element:
|
162
|
+
#
|
163
|
+
# obj.user_acl[newuser] = :read
|
164
|
+
#
|
165
|
+
# puts obj.user_acl.inspect => {user => :full, newuser => :read}
|
166
|
+
#
|
167
|
+
#
|
168
|
+
# === Modifying
|
169
|
+
# User and group permissions can be modified by modifying the appropriate key value.
|
170
|
+
# Keep in mind that you CAN be dumb and give up access to your own objects,
|
171
|
+
# even if there is no other user that has access to them.
|
172
|
+
#
|
173
|
+
# obj.user_acl[newuser] = :full
|
174
|
+
# puts obj.user_acl.inspect => {user => :full, newuser => :full}
|
175
|
+
#
|
176
|
+
# obj.group_acl['other'] = :full
|
177
|
+
# puts obj.group_acl.inspect => {other => :full}
|
178
|
+
#
|
179
|
+
#
|
180
|
+
# === Deleting
|
181
|
+
# Remove any permissions for a given user or group, you can either modify existing permissions
|
182
|
+
# to :+none+, or you can delete the user/group name from the appropriate hash. When you do either,
|
183
|
+
# the name disappears entirely from the hash.
|
184
|
+
#
|
185
|
+
# obj.user_acl.delete(newuser)
|
186
|
+
# puts obj.user_acl.inspect => {user => :full}
|
187
|
+
#
|
188
|
+
# obj.user_acl[newuser] = :none
|
189
|
+
# puts obj.user_acl.inspect => {user => :full}
|
190
|
+
#
|
191
|
+
#
|
192
|
+
class ACL < AttributeHashBase
|
193
|
+
USER = 1
|
194
|
+
GROUP = 2
|
195
|
+
|
196
|
+
#
|
197
|
+
# This constructor is only meant for internal use. To get ACLs on an object:
|
198
|
+
#
|
199
|
+
# obj.user_acl => Hash
|
200
|
+
# obj.group_acl => Hash
|
201
|
+
#
|
202
|
+
def initialize(obj, type)
|
203
|
+
raise Atmos::Exceptions::ArgumentException, "The 'obj' parameter cannot be nil." if (obj.nil?)
|
204
|
+
raise Atmos::Exceptions::ArgumentException, "The 'obj' parameter must have an id." if (obj.aoid.nil?)
|
205
|
+
raise Atmos::Exceptions::ArgumentException, "The 'type' parameter must be Atmos::ACL::USER or Atmos::ACL::GROUP." if (![USER, GROUP].include?(type))
|
206
|
+
|
207
|
+
super()
|
208
|
+
|
209
|
+
@obj = obj
|
210
|
+
@type = type
|
211
|
+
|
212
|
+
@header = (@type == USER) ? 'x-emc-useracl' : 'x-emc-groupacl'
|
213
|
+
@delete_action = @set_action = (@type == USER) ? :set_user_acl : :set_group_acl
|
214
|
+
@reload_action = :list_acl
|
215
|
+
|
216
|
+
reload(@reload_action, @obj.aoid)
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
#
|
221
|
+
# Adds or modifies permissions for a user or group.
|
222
|
+
# The change is made on the Atmos server immediately.
|
223
|
+
# Valid values are :+none+, :+read+, :+write+, :+full+.
|
224
|
+
#
|
225
|
+
def []=(key,value)
|
226
|
+
validate_value(value)
|
227
|
+
response = @obj.request.do(@set_action, :id => @obj.aoid, @header => "#{key}=#{xlate_value_from_object_to_header(value)}")
|
228
|
+
reload(@reload_action, @obj.aoid)
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
#
|
233
|
+
# Returns +true+ if this ACL object is representing user ACLs.
|
234
|
+
#
|
235
|
+
def user?
|
236
|
+
@type == USER
|
237
|
+
end
|
238
|
+
|
239
|
+
#
|
240
|
+
# Returns +true+ if this ACL object is representing group ACLs.
|
241
|
+
#
|
242
|
+
def group?
|
243
|
+
@type == GROUP
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
#
|
248
|
+
# Removes permissions for specified user/group name. Update is made on the Atmos server immediately.
|
249
|
+
#
|
250
|
+
def delete(key)
|
251
|
+
response = @obj.request.do(@set_action, :id => @obj.aoid, @header => "#{key}=#{xlate_value_from_object_to_header(:none)}")
|
252
|
+
self.delete_without_atmos(key)
|
253
|
+
reload(@reload_action, @obj.aoid)
|
254
|
+
end
|
255
|
+
|
256
|
+
#
|
257
|
+
# Removes all permissions for all groups, or for all users except the one used to instantiate
|
258
|
+
# the Atmos::Store connection.
|
259
|
+
#
|
260
|
+
def clear
|
261
|
+
# do a reload to make absolutely sure ACL is up to date
|
262
|
+
reload(@reload_action, @obj.aoid)
|
263
|
+
|
264
|
+
values = {}
|
265
|
+
self.each do |k,v|
|
266
|
+
values[k] = xlate_value_from_object_to_header(:none)
|
267
|
+
end
|
268
|
+
values.delete(@obj.user)
|
269
|
+
|
270
|
+
response = @obj.request.do(@set_action, :id => @obj.aoid, @header => Atmos::Util.hash2header(values))
|
271
|
+
reload(@reload_action, @obj.aoid)
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
private
|
276
|
+
def validate_input_hash(h)
|
277
|
+
msg = nil
|
278
|
+
bad_keys = []
|
279
|
+
bad_values = []
|
280
|
+
good_values = [:none, :read, :write, :full]
|
281
|
+
|
282
|
+
h.each do |k,v|
|
283
|
+
bad_keys.push(k) if (k.nil? || !k.kind_of?(String))
|
284
|
+
bad_values.push(v) if (v.nil? || !good_values.include?(v))
|
285
|
+
end
|
286
|
+
|
287
|
+
msg = "The input has was bad: " if (!bad_keys.empty? || !bad_values.empty?)
|
288
|
+
msg += "bad keys: #{bad_keys.inspect} " if (!bad_keys.empty?)
|
289
|
+
msg += "bad values: #{bad_values.inspect}" if (!bad_values.empty?)
|
290
|
+
|
291
|
+
raise Atmos::Exceptions::ArgumentException, msg if (!msg.nil?)
|
292
|
+
end
|
293
|
+
|
294
|
+
def validate_value(value)
|
295
|
+
if (![:none, :read, :write, :full].include?(value))
|
296
|
+
raise Atmos::Exceptions::ArgumentException, "Valid permissions values are :none, :read, :write, :full"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def xlate_value_from_header_to_object(value)
|
301
|
+
case value
|
302
|
+
when 'NONE'
|
303
|
+
:none
|
304
|
+
when 'READ'
|
305
|
+
:read
|
306
|
+
when 'WRITE'
|
307
|
+
:write
|
308
|
+
when 'FULL_CONTROL'
|
309
|
+
:full
|
310
|
+
else
|
311
|
+
raise Atmos::Exceptions::InternalLibraryException, "Permissions type not recognized: #{value}"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def xlate_value_from_object_to_header(value)
|
316
|
+
case value
|
317
|
+
when :none
|
318
|
+
'NONE'
|
319
|
+
when :read
|
320
|
+
'READ'
|
321
|
+
when :write
|
322
|
+
'WRITE'
|
323
|
+
when :full
|
324
|
+
'FULL_CONTROL'
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
# == Metadata
|
331
|
+
# In all cases, +metadata+ refers to key/value pairs, and is represented
|
332
|
+
# as a ruby Hash attached to an Atmos::Object object.
|
333
|
+
#
|
334
|
+
# === Standard Metadata
|
335
|
+
# In Atmos, +metadata+ with no modifier (e.g. +listable+ or +system+)
|
336
|
+
# refers to metadata on an object that is not indexed. In other words,
|
337
|
+
# objects cannot be referenced by the metadata information. The only
|
338
|
+
# way to get this metadata information is to already have the object
|
339
|
+
# the metadata is attached to.
|
340
|
+
#
|
341
|
+
# ==== Defaults
|
342
|
+
# By default, when you create an object, there is no metadata. So:
|
343
|
+
#
|
344
|
+
# obj.metadata => {}
|
345
|
+
#
|
346
|
+
# ==== Adding
|
347
|
+
# Add metadata in key/value pairs as you would to a hash. Keys and
|
348
|
+
# values must be ruby Strings.
|
349
|
+
#
|
350
|
+
# obj.metadata[key] = value
|
351
|
+
# obj.metadata.store(key, value)
|
352
|
+
# obj.metadata.merge!({key => value})
|
353
|
+
#
|
354
|
+
# ==== Modifying
|
355
|
+
# Modify metadata as you would a hash. Keys and values must be ruby
|
356
|
+
# Strings.
|
357
|
+
#
|
358
|
+
# obj.metadata[pre-existing-key] = new-value
|
359
|
+
# obj.metadata.store(pre-existing-key, new-value)
|
360
|
+
# obj.metadata.merge!({pre-existing-key => new-value})
|
361
|
+
#
|
362
|
+
# ==== Deleting
|
363
|
+
# Delete metadata as you would a hash. Keys must be ruby
|
364
|
+
# Strings.
|
365
|
+
#
|
366
|
+
# obj.metadata.delete(key)
|
367
|
+
# obj.metadata.clear
|
368
|
+
#
|
369
|
+
# === Listable Metadata
|
370
|
+
# Listable metadata means atmos indexes the object by the key in the
|
371
|
+
# key/value metadata pair. The keys of listable metadata are also known
|
372
|
+
# as listable tags. Currently, an Atmos server can handle about 10k listable
|
373
|
+
# tags easily, so use judiciously with very large datasets.
|
374
|
+
#
|
375
|
+
# ==== Defaults
|
376
|
+
# By default, when you create an object, there is no listable metadata. So:
|
377
|
+
#
|
378
|
+
# obj.listable_metadata => {}
|
379
|
+
#
|
380
|
+
# ==== Adding
|
381
|
+
# Add metadata in key/value pairs as you would to a hash. Keys and
|
382
|
+
# values must be ruby Strings.
|
383
|
+
#
|
384
|
+
# obj.listable_metadata[key] = value
|
385
|
+
# obj.listable_metadata.store(key, value)
|
386
|
+
# obj.listable_metadata.merge!({key => value})
|
387
|
+
#
|
388
|
+
# ==== Modifying
|
389
|
+
# Modify metadata as you would a hash. Keys and values must be ruby
|
390
|
+
# Strings.
|
391
|
+
#
|
392
|
+
# obj.listable_metadata[pre-existing-key] = new-value
|
393
|
+
# obj.listable_metadata.store(pre-existing-key, new-value)
|
394
|
+
# obj.listable_metadata.merge!({pre-existing-key => new-value})
|
395
|
+
#
|
396
|
+
# ==== Deleting
|
397
|
+
# Delete metadata as you would a hash. Keys must be ruby
|
398
|
+
# Strings.
|
399
|
+
#
|
400
|
+
# obj.listable_metadata.delete(key)
|
401
|
+
# obj.listable_metadata.clear
|
402
|
+
#
|
403
|
+
# === System Metadata
|
404
|
+
# System metadata is a standard group of information maintained by Atmos for each object,
|
405
|
+
# such as +atime+, +mtime+, +type+, +policyname+.
|
406
|
+
#
|
407
|
+
# System metadata is not modifiable.
|
408
|
+
#
|
409
|
+
class Metadata < AttributeHashBase
|
410
|
+
LISTABLE = 1
|
411
|
+
NON_LISTABLE = 2
|
412
|
+
SYSTEM = 3
|
413
|
+
|
414
|
+
|
415
|
+
#
|
416
|
+
# This constructor is only meant for internal use. To get the metadata on an object:
|
417
|
+
#
|
418
|
+
# obj.metadata => Hash
|
419
|
+
# obj.listable_metadata => Hash
|
420
|
+
# obj.system_metadata => Hash
|
421
|
+
#
|
422
|
+
def initialize(obj, type)
|
423
|
+
raise Atmos::Exceptions::ArgumentException, "The 'obj' parameter cannot be nil." if (obj.nil?)
|
424
|
+
raise Atmos::Exceptions::ArgumentException, "The 'obj' parameter must have an id." if (obj.aoid.nil?)
|
425
|
+
raise Atmos::Exceptions::ArgumentException, "The 'type' parameter must be one of Atmos::Metadata::LISTABLE Atmos::Metadata::NON_LISTABLE Atmos::Metadata::SYSTEM." if (![SYSTEM, LISTABLE, NON_LISTABLE].include?(type))
|
426
|
+
|
427
|
+
super()
|
428
|
+
|
429
|
+
@obj = obj
|
430
|
+
@type = type
|
431
|
+
|
432
|
+
@header = (@type == LISTABLE) ? 'x-emc-listable-meta' : 'x-emc-meta'
|
433
|
+
@reload_action = (@type == SYSTEM) ? :list_system_metadata : :list_metadata
|
434
|
+
@set_action = (@type == LISTABLE) ? :set_listable_metadata : :set_metadata
|
435
|
+
|
436
|
+
reload(@reload_action, @obj.aoid)
|
437
|
+
end
|
438
|
+
|
439
|
+
|
440
|
+
#
|
441
|
+
# Adds the specified metadata to the object unless the object is representing system metadata.
|
442
|
+
#
|
443
|
+
# System metadata cannot be modified, so an Atmos::Exceptions::NotImplementedException
|
444
|
+
# is thrown.
|
445
|
+
#
|
446
|
+
# The change is made on the Atmos server immediately.
|
447
|
+
#
|
448
|
+
def []=(key,value)
|
449
|
+
raise Atmos::Exceptions::NotImplementedException, "System metadata cannot be modified." if (@type == SYSTEM)
|
450
|
+
raise Atmos::Exceptions::ArgumentException, "The 'value' parameter must be of type String'." if (!value.nil? && !value.kind_of?(String))
|
451
|
+
response = @obj.request.do(@set_action, :id => @obj.aoid, @header => "#{key}=#{value}")
|
452
|
+
reload(@reload_action, @obj.aoid)
|
453
|
+
end
|
454
|
+
|
455
|
+
|
456
|
+
#
|
457
|
+
# Deletes all metadata from the object unless the object is representing system metadata.
|
458
|
+
#
|
459
|
+
# System metadata cannot be modified, so an Atmos::Exceptions::NotImplementedException
|
460
|
+
# is thrown.
|
461
|
+
#
|
462
|
+
# The change is made on the Atmos server immediately.
|
463
|
+
#
|
464
|
+
def clear
|
465
|
+
raise Atmos::Exceptions::NotImplementedException, "System metadata cannot be modified." if (@type == SYSTEM)
|
466
|
+
reload(@reload_action, @obj.aoid)
|
467
|
+
response = @obj.request.do(:delete_metadata, :id => @obj.aoid, 'x-emc-tags' => self.keys.join(','))
|
468
|
+
self.clear_without_atmos
|
469
|
+
reload(@reload_action, @obj.aoid)
|
470
|
+
end
|
471
|
+
|
472
|
+
|
473
|
+
#
|
474
|
+
# Deletes the specified metadata from the object unless the
|
475
|
+
# object is representing system metadata. System metadata cannot
|
476
|
+
# be modified, so an <tt>Atmos::Exceptions::NotImplementedException</tt>
|
477
|
+
# is thrown.
|
478
|
+
#
|
479
|
+
# The deleted is executed on the Atmos server immediately.
|
480
|
+
#
|
481
|
+
# Required:
|
482
|
+
# *<tt>key</tt> -
|
483
|
+
def delete(key)
|
484
|
+
raise Atmos::Exceptions::NotImplementedException, "System metadata cannot be modified." if (@type == SYSTEM)
|
485
|
+
response = @obj.request.do(:delete_metadata, :id => @obj.aoid, 'x-emc-tags' => key)
|
486
|
+
self.delete_without_atmos(key)
|
487
|
+
reload(@reload_action, @obj.aoid)
|
488
|
+
end
|
489
|
+
|
490
|
+
|
491
|
+
#
|
492
|
+
# Returns +true+ if this Metadata object is representing listable metadata.
|
493
|
+
#
|
494
|
+
def listable?
|
495
|
+
@type == LISTABLE
|
496
|
+
end
|
497
|
+
|
498
|
+
|
499
|
+
#
|
500
|
+
# Returns +true+ if this Metadata object is representing non-listable metadata.
|
501
|
+
#
|
502
|
+
def non_listable?
|
503
|
+
@type == NON_LISTABLE
|
504
|
+
end
|
505
|
+
|
506
|
+
|
507
|
+
#
|
508
|
+
# Returns +true+ if this Metadata object is representing system metadata.
|
509
|
+
#
|
510
|
+
def system?
|
511
|
+
@type == SYSTEM
|
512
|
+
end
|
513
|
+
|
514
|
+
|
515
|
+
private
|
516
|
+
#
|
517
|
+
# This method is defined to override AttributeHashBase.validate_input_hash,
|
518
|
+
# which simply returns true. This allows the base class to contain the
|
519
|
+
# method definitions for several standard Hash functions, yet allows the
|
520
|
+
# validation to be specialized for each subclass.
|
521
|
+
#
|
522
|
+
# Required:
|
523
|
+
# *<tt>h</tt> - the hash to validate
|
524
|
+
#
|
525
|
+
def validate_input_hash(h)
|
526
|
+
msg = nil
|
527
|
+
bad_keys = []
|
528
|
+
bad_values = []
|
529
|
+
good_values = [:none, :read, :write, :full]
|
530
|
+
|
531
|
+
h.each do |k,v|
|
532
|
+
bad_keys.push(k) if (k.nil? || !k.kind_of?(String))
|
533
|
+
bad_values.push(v) if (v.nil? || !good_values.include?(v))
|
534
|
+
end
|
535
|
+
|
536
|
+
msg = "The input has was bad: " if (!bad_keys.empty? || !bad_values.empty?)
|
537
|
+
msg += "bad keys: #{bad_keys.inspect} " if (!bad_keys.empty?)
|
538
|
+
msg += "bad values: #{bad_values.inspect}" if (!bad_values.empty?)
|
539
|
+
|
540
|
+
raise Atmos::Exceptions::ArgumentException, msg if (!msg.nil?)
|
541
|
+
end
|
542
|
+
|
543
|
+
end
|
544
|
+
|
545
|
+
end
|