duracloud-client 0.0.3 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 597a70d0d1ce9abc360fe01770e5dd4ed058604c
4
- data.tar.gz: ef764b6b7e68c8dfe9899ff46f5ab3f325a006d5
3
+ metadata.gz: 16480155d0d2f9036d3f8c199f5a46d2b85f7a25
4
+ data.tar.gz: 573389d2175aa32650c97f06933688dee8526bf3
5
5
  SHA512:
6
- metadata.gz: f7da0a6da94606f9d25e3164c84999c602d7ece4c9ea62930abe5b3b7816f0f551ef797303a04a3b6af7ed48e8509b66925121c9a3e38cc6c27746c59035ae77
7
- data.tar.gz: e05f77e6a948cf0008ccddb0de25f0f9ed0c07d5fc1a9f5b77796c9e0fee6f4ab7d0341f9a31d6ca283bcdd36ff18a3fe217b05fe5ee7d9fcb3e0a81b4b2c9b6
6
+ metadata.gz: 06f61f2484247a0f729d96bdefda8836fbe5220b8e2986c5ddf6bbc56e8f9add646161d70d7b5664307f6fdc32e2cfbf5690371c15ae67f29655dd11c41bd2a1
7
+ data.tar.gz: 8c3b83a4b8fc005e24cccac9346a1b95318ec90adc881b2d6d24a9ac311775b7463f6442cbb142f10fcf9f2ebee0a616030d94e072997425811bbd4d1427809a
data/README.md CHANGED
@@ -40,7 +40,7 @@ end
40
40
 
41
41
  ```
42
42
  > c = Duracloud::Client.new
43
- => #<Duracloud::Client:0x007fe953a1c630 @config=#<Duracloud::Configuration host="foo.duracloud.org", port=nil, user="bob@example.com", password="******">>
43
+ => #<Duracloud::Client:0x007fe953a1c630 @config=#<Duracloud::Configuration host="foo.duracloud.org", port=nil, user="bob@example.com">>
44
44
  ```
45
45
 
46
46
  #### Logging
@@ -53,29 +53,80 @@ Duracloud::Client.configure do |config|
53
53
  end
54
54
  ```
55
55
 
56
+ You can also silence logging:
57
+
58
+ ```ruby
59
+ Duracloud::Client.configure do |config|
60
+ config.silence_logging! # sets logger device to null device
61
+ end
62
+ ```
63
+
56
64
  ### List Storage Providers
57
65
 
58
66
  ```
59
- > stores = Duracloud::Store.all
67
+ >> stores = Duracloud::Store.all
60
68
  => [#<Duracloud::Store:0x007faa592e9068 @owner_id="0", @primary="0", @id="1", @provider_type="AMAZON_GLACIER">, #<Duracloud::Store:0x007faa592dbd78 @owner_id="0", @primary="1", @id="2", @provider_type="AMAZON_S3">]
61
69
 
62
- > stores.first.primary?
63
- => false
70
+ >> stores.first.primary?
71
+ => false
72
+
73
+ >> Duracloud::Store.primary
74
+ => #<Duracloud::Store:0x007faa592dbd78 @owner_id="0", @primary="1", @id="2", @provider_type="AMAZON_S3">
64
75
  ```
65
76
 
66
- ### Space Methods
77
+ ### Spaces
67
78
 
68
- TODO
79
+ #### Create a new space
69
80
 
70
- ### Content Methods
81
+ ```
82
+ >> space = Duracloud::Space.create("rest-api-testing2")
83
+ D, [2016-04-29T12:12:32.641574 #28275] DEBUG -- : Duracloud::Client PUT https://foo.duracloud.org/durastore/rest-api-testing2 201 Created
84
+ => #<Duracloud::Space space_id="rest-api-testing2", store_id="(default)">
85
+ ```
71
86
 
72
- #### Create a new content item and store it in DuraCloud
87
+ A `Duracloud::BadRequestError` is raise if the space ID is invalid (illegal characters, too long, etc.).
73
88
 
74
- 1. Initialize instance of `Duracloud::Content` and save:
89
+ #### Retrieve a space and view its properties
75
90
 
76
91
  ```
77
- >> new_content = Duracloud::Content.new(space_id: "rest-api-testing", id: "ark:/99999/fk4zzzz")
78
- => #<Duracloud::Content space_id="rest-api-testing", id="ark:/99999/fk4zzzz">
92
+ >> space = Duracloud::Space.find("rest-api-testing")
93
+ D, [2016-04-29T12:15:12.593075 #28275] DEBUG -- : Duracloud::Client HEAD https://foo.duracloud.org/durastore/rest-api-testing 200 OK
94
+ => #<Duracloud::Space space_id="rest-api-testing", store_id="(default)">
95
+
96
+ >> space.count
97
+ => 8
98
+
99
+ >> space.created
100
+ => #<DateTime: 2016-04-05T17:59:11+00:00 ((2457484j,64751s,0n),+0s,2299161j)>
101
+ ```
102
+
103
+ A `Duracloud::NotFoundError` exception is raise if the space does not exist.
104
+
105
+ #### Enumerate the content IDs of the space
106
+
107
+ ```
108
+ > space.content_ids.each { |id| puts id }
109
+ ark:/99999/fk4zzzz
110
+ foo
111
+ foo2
112
+ foo22
113
+ foo3
114
+ foo5
115
+ foo7
116
+ foo8
117
+ => nil
118
+
119
+ > space.content_ids.to_a
120
+ => ["ark:/99999/fk4zzzz", "foo", "foo2", "foo22", "foo3", "foo5", "foo7", "foo8"]
121
+ ```
122
+
123
+ ### Content
124
+
125
+ #### Create a new content item and store it in DuraCloud
126
+
127
+ ```
128
+ >> new_content = Duracloud::Content.new("rest-api-testing", "ark:/99999/fk4zzzz")
129
+ => #<Duracloud::Content space_id="rest-api-testing", content_id="ark:/99999/fk4zzzz", store_id=(default)>
79
130
 
80
131
  >> new_content.body = "test"
81
132
  => "test"
@@ -84,32 +135,62 @@ TODO
84
135
  => "text/plain"
85
136
 
86
137
  >> new_content.save
87
- => #<Duracloud::Content space_id="rest-api-testing", id="ark:/99999/fk4zzzz">
138
+ => #<Duracloud::Content space_id="rest-api-testing", content_id="ark:/99999/fk4zzzz", store_id=(default)>
88
139
  ```
89
140
 
90
- 2. Create with class method `Duracloud::Content.create`:
141
+ When storing content a `Duracloud::NotFoundError` is raised if the space does not exist. A `Duracloud::BadRequestError` is raised if the content ID is invalid.
142
+
143
+ #### Retrieve an existing content item from DuraCloud
91
144
 
92
145
  ```
93
- >> Duracloud::Content.create(space_id: "rest-api-testing", id="ark:/99999/fk4zzzz") do |c|
94
- c.body = "test"
95
- c.content_type = "text/plain"
96
- end
97
- => #<Duracloud::Content space_id="rest-api-testing", id="ark:/99999/fk4zzzz">
146
+ >> Duracloud::Content.find("spaceID", "contentID")
147
+ => #<Duracloud::Content space_id="spaceID", content_id="contentID", store_id=(default)>
98
148
  ```
99
149
 
100
- #### Retrieve an existing content item from DuraCloud
150
+ If the space or content ID does not exist, a `Duracloud::NotFoundError` is raised.
151
+
152
+ #### Update the properties for a content item
101
153
 
102
- ```ruby
103
- Duracloud::Content.find(id: "contentID", space_id: "spaceID")
104
154
  ```
155
+ >> space = Duracloud::Space.find("rest-api-testing")
156
+ => #<Duracloud::Space space_id="rest-api-testing", store_id="(default)">
157
+
158
+ >> content = space.find_content("foo3")
159
+ D, [2016-04-29T18:31:16.975749 #32379] DEBUG -- : Duracloud::Client HEAD https://foo.duracloud.org/durastore/rest-api-testing/foo3 200 OK
160
+ => #<Duracloud::Content space_id="rest-api-testing", content_id="foo3", store_id=(default)>
105
161
 
106
- #### Update the properties for an item
162
+ >> content.properties
163
+ => #<Duracloud::ContentProperties x-dura-meta-owner="ellen@example.com">
107
164
 
108
- TODO
165
+ >> content.properties.creator = "bob@example.com"
166
+ >> content.save
167
+ D, [2016-04-29T18:31:52.770195 #32379] DEBUG -- : Duracloud::Client POST https://foo.duracloud.org/durastore/rest-api-testing/foo3 200 OK
168
+ I, [2016-04-29T18:31:52.770293 #32379] INFO -- : Content foo3 updated successfully
169
+ => true
170
+
171
+ >> content.properties.creator
172
+ D, [2016-04-29T18:32:06.465928 #32379] DEBUG -- : Duracloud::Client HEAD https://foo.duracloud.org/durastore/rest-api-testing/foo3 200 OK
173
+ => "bob@example.com"
174
+ ```
109
175
 
110
176
  #### Delete a content item
111
177
 
112
- TODO
178
+ ```
179
+ >> space = Duracloud::Space.find("rest-api-testing")
180
+ => #<Duracloud::Space space_id="rest-api-testing", store_id="(default)">
181
+
182
+ >> content = space.find_content("foo2")
183
+ => #<Duracloud::Content space_id="rest-api-testing", content_id="foo2", store_id=(default)>
184
+
185
+ >> content.delete
186
+ D, [2016-04-29T18:28:31.459962 #32379] DEBUG -- : Duracloud::Client DELETE https://foo.duracloud.org/durastore/rest-api-testing/foo2 200 OK
187
+ I, [2016-04-29T18:28:31.460069 #32379] INFO -- : Content foo2 deleted successfully
188
+ => #<Duracloud::Content space_id="rest-api-testing", content_id="foo2", store_id=(default)>
189
+
190
+ >> Duracloud::Content.exist?("rest-api-testing", "foo2")
191
+ D, [2016-04-29T18:29:03.935451 #32379] DEBUG -- : Duracloud::Client HEAD https://foo.duracloud.org/durastore/rest-api-testing/foo2 404 Not Found
192
+ => false
193
+ ```
113
194
 
114
195
  ## Versioning
115
196
 
data/duracloud.gemspec CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.add_dependency "activemodel", "~> 4.2"
26
26
  spec.add_dependency "nokogiri", "~> 1.6"
27
27
 
28
+ spec.add_development_dependency "webmock", "~> 2.0"
28
29
  spec.add_development_dependency "rspec", "~> 3.4"
29
30
  spec.add_development_dependency "rspec-its", "~> 1.2"
30
31
  spec.add_development_dependency "bundler", "~> 1.7"
@@ -0,0 +1,35 @@
1
+ module Duracloud
2
+ class AuditLog
3
+
4
+ attr_reader :space_id, :store_id
5
+
6
+ def initialize(space_id, store_id = nil)
7
+ @space_id = space_id
8
+ @store_id = store_id
9
+ @response = nil
10
+ end
11
+
12
+ def csv(opts = {})
13
+ CSVReader.call(tsv, opts)
14
+ end
15
+
16
+ def tsv
17
+ response.body
18
+ end
19
+
20
+ def to_s
21
+ tsv
22
+ end
23
+
24
+ private
25
+
26
+ def response
27
+ @response ||= Client.get_manifest(space_id, **query)
28
+ end
29
+
30
+ def query
31
+ { storeID: store_id }
32
+ end
33
+
34
+ end
35
+ end
@@ -1,5 +1,4 @@
1
1
  require "date"
2
- require "csv"
3
2
 
4
3
  module Duracloud
5
4
  class BitIntegrityReport
@@ -7,56 +6,71 @@ module Duracloud
7
6
  SUCCESS = "SUCCESS".freeze
8
7
  FAILURE = "FAILURE".freeze
9
8
 
10
- CSV_OPTS = {
11
- col_sep: '\t',
12
- headers: :first_row,
13
- write_headers: true,
14
- return_headers: true,
15
- }
9
+ COMPLETION_DATE_HEADER = "Bit-Integrity-Report-Completion-Date".freeze
10
+ RESULT_HEADER = "Bit-Integrity-Report-Result".freeze
16
11
 
17
- def self.success?(space_id)
18
- new(space_id).success?
19
- end
20
-
21
- attr_reader :space_id
12
+ attr_reader :space_id, :store_id
22
13
 
23
- def initialize(space_id)
14
+ def initialize(space_id, store_id = nil)
24
15
  @space_id = space_id
16
+ @store_id = store_id
17
+ @report, @properties = nil, nil
25
18
  end
26
19
 
27
- def data
20
+ def tsv
28
21
  report.body
29
22
  end
30
23
 
31
24
  def completion_date
32
- DateTime.parse(properties["Bit-Integrity-Report-Completion-Date"].first)
25
+ DateTime.parse(properties[COMPLETION_DATE_HEADER].first)
33
26
  end
34
27
 
35
28
  def result
36
- properties["Bit-Integrity-Report-Result"].first
29
+ properties[RESULT_HEADER].first
37
30
  end
38
31
 
39
32
  def csv(opts = {})
40
- CSV.new(data, CSV_OPTS.merge(opts))
33
+ CSVReader.new(tsv, opts)
41
34
  end
42
35
 
43
36
  def success?
44
37
  result == SUCCESS
45
38
  end
46
39
 
47
- private
48
-
49
40
  def report
50
- @report ||= Client.get_bit_integrity_report(space_id)
41
+ @report ||= fetch_report
42
+ end
43
+
44
+ def report_loaded?
45
+ !@report.nil?
51
46
  end
52
47
 
53
48
  def properties
54
- @properties ||= if @report
55
- report.headers
56
- else
57
- response = Client.get_bit_integrity_report_properties(space_id)
58
- response.headers
59
- end
49
+ @properties ||= fetch_properties
50
+ end
51
+
52
+ private
53
+
54
+ def fetch_report
55
+ reset_properties
56
+ Client.get_bit_integrity_report(space_id, **query)
57
+ end
58
+
59
+ def reset_properties
60
+ @properties = nil
61
+ end
62
+
63
+ def fetch_properties
64
+ if report_loaded?
65
+ report.headers
66
+ else
67
+ response = Client.get_bit_integrity_report_properties(space_id, **query)
68
+ response.headers
69
+ end
70
+ end
71
+
72
+ def query
73
+ { storeID: store_id }
60
74
  end
61
75
 
62
76
  end
@@ -6,6 +6,10 @@ module Duracloud
6
6
 
7
7
  class << self
8
8
  attr_accessor :host, :port, :user, :password, :logger
9
+
10
+ def silence_logging!
11
+ self.logger = Logger.new(File::NULL)
12
+ end
9
13
  end
10
14
 
11
15
  attr_reader :host, :port, :user, :password, :logger
@@ -15,7 +19,7 @@ module Duracloud
15
19
  @port = port || default(:port)
16
20
  @user = user || default(:user)
17
21
  @password = password || default(:password)
18
- @logger = logger || Logger.new(STDERR)
22
+ @logger = logger || self.class.logger || Logger.new(STDERR)
19
23
  freeze
20
24
  end
21
25
 
@@ -24,8 +28,8 @@ module Duracloud
24
28
  end
25
29
 
26
30
  def inspect
27
- "#<#{self.class} host=#{host.inspect}, port=#{port.inspect}, user=#{user.inspect}," \
28
- " password=\"******\", logger=#{logger.inspect}>"
31
+ "#<#{self.class} host=#{host.inspect}, port=#{port.inspect}," \
32
+ " user=#{user.inspect}>"
29
33
  end
30
34
 
31
35
  private
@@ -1,4 +1,3 @@
1
- require "uri"
2
1
  require "stringio"
3
2
  require "active_model"
4
3
 
@@ -13,67 +12,59 @@ module Duracloud
13
12
 
14
13
  after_save :changes_applied
15
14
 
16
- # Find content in DuraCloud.
17
- #
18
- # @param id [String] the content ID
19
- # @param space_id [String] the space ID.
20
- # @return [Duraclound::Content] the content
21
- # @raise [Duracloud::NotFoundError] the space or content ID does not exist.
22
- def self.find(id:, space_id:)
23
- new(id: id, space_id: space_id) do |content|
24
- content.load_properties
25
- end
15
+ # Does the content exist in DuraCloud?
16
+ # @see .new for arguments
17
+ # @return [Boolean] whether the content exists
18
+ def self.exist?(*args)
19
+ find(*args) && true
20
+ rescue NotFoundError
21
+ false
26
22
  end
27
23
 
28
- # Store content in DuraCloud
29
- #
30
- # @param id [String] The content ID
31
- # @param space_id [String] The space ID.
32
- # @param body [String, #read] The content body
33
- # @return [Duracloud::Content] the content
34
- # @raise [Duracloud::NotFoundError] if the space ID does not exist
35
- # @raise [Duracloud::Error] if the body is empty.
36
- def self.create(id:, space_id:, body:)
37
- new(id: id, space_id: space_id) do |content|
38
- content.body = body
39
- yield content if block_given?
40
- content.save
41
- end
24
+ # Find content in DuraCloud.
25
+ # @see .new for arguments
26
+ # @return [Duraclound::Content] the content
27
+ # @raise [Duracloud::NotFoundError] the space, content, or store does not exist.
28
+ def self.find(*args)
29
+ new(*args) { |content| content.load_properties }
42
30
  end
43
31
 
44
- attr_reader :id, :space_id
32
+ attr_reader :space_id, :content_id, :store_id
33
+ alias_method :id, :content_id
45
34
 
46
- define_attribute_methods :content_type, :body, :md5
35
+ define_attribute_methods :content_type, :body
47
36
 
48
- # Initialize a new piece of content
49
- #
50
- # @param id [String] The content ID
51
- # @param space_id [String] The space ID
52
- #
37
+ # @param space_id [String] The space ID (required)
38
+ # @param content_id [String] The content ID (required)
39
+ # @param store_id [String] the store ID (optional)
53
40
  # @example
54
- # new(id: mycontent.txt", space_id: "myspace")
55
- def initialize(id:, space_id:)
56
- @id = id.freeze
57
- @space_id = space_id.freeze
58
- @body = nil
59
- @content_type = nil
60
- @md5 = nil
41
+ # new("myspace", "mycontent.txt")
42
+ def initialize(space_id, content_id, store_id = nil)
43
+ @content_id = content_id
44
+ @space_id = space_id
45
+ @store_id = store_id
46
+ @body, @content_type = nil, nil
61
47
  yield self if block_given?
62
48
  end
63
49
 
50
+ # Return the space associated with this content.
51
+ # @return [Duracloud::Space] the space.
52
+ # @raise [Duracloud::NotFoundError] the space or store does not exist.
64
53
  def space
65
- Space.find(space_id)
54
+ Space.find(space_id, store_id)
66
55
  end
67
56
 
68
57
  def inspect
69
- "#<#{self.class} id=#{id.inspect}, space_id=#{space_id.inspect}>"
58
+ "#<#{self.class} space_id=#{space_id.inspect}," \
59
+ " content_id=#{content_id.inspect}," \
60
+ " store_id=#{store_id || '(default)'}>"
70
61
  end
71
62
 
72
63
  # @api private
73
64
  # @raise [Duracloud::NotFoundError] the content does not exist in DuraCloud.
74
65
  def load_body
75
- response = Client.get_content(url)
76
- @body = response.body # don't use setter
66
+ response = Client.get_content(*args, **query)
67
+ set_body(response) # don't use setter b/c marks as dirty
77
68
  persisted!
78
69
  end
79
70
 
@@ -85,18 +76,19 @@ module Duracloud
85
76
  end
86
77
 
87
78
  def body=(str_or_io)
88
- val = read_string_or_io(str_or_io)
89
- raise ArgumentError, "Cannot set body to empty string." if val.empty?
90
- self.md5 = Digest::MD5.hexdigest(val)
91
- body_will_change! if md5_changed?
92
- @body = StringIO.new(val, "r")
79
+ set_body(str_or_io)
80
+ body_will_change!
93
81
  end
94
82
 
83
+ # Return the content body, loading from DuraCloud if necessary.
84
+ # @return [String, StringIO] the content body
95
85
  def body
96
86
  load_body if persisted? && empty?
97
87
  @body
98
88
  end
99
89
 
90
+ # Is the content empty?
91
+ # @return [Boolean] whether the content is empty (nil or empty string)
100
92
  def empty?
101
93
  @body.nil? || @body.size == 0
102
94
  end
@@ -110,34 +102,34 @@ module Duracloud
110
102
  @content_type
111
103
  end
112
104
 
113
- def md5
114
- @md5
115
- end
116
-
117
105
  private
118
106
 
119
- def md5=(val)
120
- md5_will_change! unless val == @md5
121
- @md5 = val
107
+ def set_body(str_or_io)
108
+ @body = StringIO.new(read_string_or_io(str_or_io), "r")
122
109
  end
123
110
 
124
111
  def set_properties
125
112
  headers = properties.to_h
126
113
  headers["Content-Type"] = content_type if content_type_changed?
127
- response = Client.set_content_properties(url, headers: headers)
128
- # response.body is a text message -- log?
114
+ options = { headers: headers, query: query }
115
+ Client.set_content_properties(*args, **options)
129
116
  end
130
117
 
131
118
  def store
132
- headers = { "Content-MD5" => md5,
133
- "Content-Type" => content_type || "application/octet-stream" }
119
+ headers = {
120
+ "Content-MD5" => md5,
121
+ "Content-Type" => content_type || "application/octet-stream"
122
+ }
134
123
  headers.merge!(properties)
135
- response = Client.store_content(url, body: body, headers: headers)
136
- # response.body is a text message -- log?
124
+ options = { body: body, headers: headers, query: query }
125
+ Client.store_content(*args, **options)
137
126
  end
138
127
 
139
- def url
140
- [space_id, id].join("/")
128
+ def md5
129
+ body.rewind
130
+ Digest::MD5.hexdigest(body.read)
131
+ ensure
132
+ body.rewind
141
133
  end
142
134
 
143
135
  def properties_class
@@ -145,11 +137,11 @@ module Duracloud
145
137
  end
146
138
 
147
139
  def get_properties_response
148
- Client.get_content_properties(url)
140
+ Client.get_content_properties(*args, **query)
149
141
  end
150
142
 
151
143
  def do_delete
152
- Client.delete_content(url)
144
+ Client.delete_content(*args, **query)
153
145
  end
154
146
 
155
147
  def do_save
@@ -181,5 +173,13 @@ module Duracloud
181
173
  end
182
174
  end
183
175
 
176
+ def args
177
+ [ space_id, content_id ]
178
+ end
179
+
180
+ def query
181
+ { storeID: store_id }
182
+ end
183
+
184
184
  end
185
185
  end
@@ -0,0 +1,18 @@
1
+ require "csv"
2
+
3
+ module Duracloud
4
+ class CSVReader
5
+
6
+ CSV_OPTS = {
7
+ col_sep: '\t',
8
+ headers: :first_row,
9
+ write_headers: true,
10
+ return_headers: true,
11
+ }
12
+
13
+ def self.call(data, opts = {})
14
+ CSV.new(data, CSV_OPTS.merge(opts))
15
+ end
16
+
17
+ end
18
+ end
@@ -1,6 +1,6 @@
1
1
  module Duracloud
2
2
  class DurastoreRequest < Request
3
- def base_path
3
+ def base_path
4
4
  '/durastore/'
5
5
  end
6
6
  end
@@ -2,6 +2,6 @@ module Duracloud
2
2
  class Error < ::StandardError; end
3
3
  class ServerError < Error; end
4
4
  class NotFoundError < Error; end
5
- class ChecksumError < Error; end
6
- class InvalidContentIDError < Error; end
5
+ class BadRequestError < Error; end
6
+ class ConflictError < Error; end
7
7
  end
@@ -37,10 +37,18 @@ module Duracloud
37
37
  Error
38
38
  end
39
39
 
40
+ def handle_400
41
+ BadRequestError
42
+ end
43
+
40
44
  def handle_404
41
45
  NotFoundError
42
46
  end
43
47
 
48
+ def handle_409
49
+ ConflictError
50
+ end
51
+
44
52
  def response_has_error_message?
45
53
  response.plain_text? && response.has_body?
46
54
  end
@@ -10,11 +10,18 @@ module Duracloud
10
10
  end
11
11
  end
12
12
 
13
+ # Return the properties associated with this resource,
14
+ # loading from Duracloud if necessary.
15
+ # @return [Duracloud::Properties] the properties
16
+ # @raise [Duracloud::NotFoundError] if the resource is marked persisted
17
+ # but does not exist in Duracloud
13
18
  def properties
14
19
  load_properties if persisted? && @properties.nil?
15
20
  @properties ||= properties_class.new
16
21
  end
17
22
 
23
+ # @api private
24
+ # @raise [Duracloud::NotFoundError] the resource does not exist in DuraCloud.
18
25
  def load_properties
19
26
  response = get_properties_response
20
27
  self.properties = response.headers