duracloud-client 0.0.3 → 0.1.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
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