aws-sdk-s3 1.66.0 → 1.69.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aws-sdk-s3.rb +3 -1
  3. data/lib/aws-sdk-s3/bucket.rb +2 -0
  4. data/lib/aws-sdk-s3/bucket_acl.rb +2 -0
  5. data/lib/aws-sdk-s3/bucket_cors.rb +2 -0
  6. data/lib/aws-sdk-s3/bucket_lifecycle.rb +2 -0
  7. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +2 -0
  8. data/lib/aws-sdk-s3/bucket_logging.rb +2 -0
  9. data/lib/aws-sdk-s3/bucket_notification.rb +2 -0
  10. data/lib/aws-sdk-s3/bucket_policy.rb +2 -0
  11. data/lib/aws-sdk-s3/bucket_region_cache.rb +2 -0
  12. data/lib/aws-sdk-s3/bucket_request_payment.rb +2 -0
  13. data/lib/aws-sdk-s3/bucket_tagging.rb +2 -0
  14. data/lib/aws-sdk-s3/bucket_versioning.rb +2 -0
  15. data/lib/aws-sdk-s3/bucket_website.rb +2 -0
  16. data/lib/aws-sdk-s3/client.rb +12 -7
  17. data/lib/aws-sdk-s3/client_api.rb +22 -0
  18. data/lib/aws-sdk-s3/customizations.rb +3 -0
  19. data/lib/aws-sdk-s3/customizations/bucket.rb +2 -0
  20. data/lib/aws-sdk-s3/customizations/multipart_upload.rb +2 -0
  21. data/lib/aws-sdk-s3/customizations/object.rb +2 -0
  22. data/lib/aws-sdk-s3/customizations/object_summary.rb +2 -0
  23. data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +2 -0
  24. data/lib/aws-sdk-s3/encryption.rb +2 -0
  25. data/lib/aws-sdk-s3/encryption/client.rb +3 -1
  26. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +11 -0
  27. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +2 -0
  28. data/lib/aws-sdk-s3/encryption/default_key_provider.rb +2 -0
  29. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +11 -0
  30. data/lib/aws-sdk-s3/encryption/errors.rb +2 -0
  31. data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +2 -0
  32. data/lib/aws-sdk-s3/encryption/io_decrypter.rb +8 -1
  33. data/lib/aws-sdk-s3/encryption/io_encrypter.rb +2 -0
  34. data/lib/aws-sdk-s3/encryption/key_provider.rb +2 -0
  35. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +2 -0
  36. data/lib/aws-sdk-s3/encryption/materials.rb +2 -0
  37. data/lib/aws-sdk-s3/encryption/utils.rb +2 -0
  38. data/lib/aws-sdk-s3/encryptionV2/client.rb +388 -0
  39. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +198 -0
  40. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +103 -0
  41. data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +38 -0
  42. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +66 -0
  43. data/lib/aws-sdk-s3/encryptionV2/errors.rb +13 -0
  44. data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +56 -0
  45. data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +30 -0
  46. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +67 -0
  47. data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +29 -0
  48. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +99 -0
  49. data/lib/aws-sdk-s3/encryptionV2/materials.rb +58 -0
  50. data/lib/aws-sdk-s3/encryptionV2/utils.rb +116 -0
  51. data/lib/aws-sdk-s3/encryption_v2.rb +20 -0
  52. data/lib/aws-sdk-s3/errors.rb +2 -0
  53. data/lib/aws-sdk-s3/event_streams.rb +7 -0
  54. data/lib/aws-sdk-s3/file_downloader.rb +2 -0
  55. data/lib/aws-sdk-s3/file_part.rb +2 -0
  56. data/lib/aws-sdk-s3/file_uploader.rb +2 -0
  57. data/lib/aws-sdk-s3/legacy_signer.rb +2 -0
  58. data/lib/aws-sdk-s3/multipart_file_uploader.rb +2 -0
  59. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +2 -0
  60. data/lib/aws-sdk-s3/multipart_upload.rb +2 -0
  61. data/lib/aws-sdk-s3/multipart_upload_error.rb +2 -0
  62. data/lib/aws-sdk-s3/multipart_upload_part.rb +2 -0
  63. data/lib/aws-sdk-s3/object.rb +2 -0
  64. data/lib/aws-sdk-s3/object_acl.rb +2 -0
  65. data/lib/aws-sdk-s3/object_copier.rb +2 -0
  66. data/lib/aws-sdk-s3/object_multipart_copier.rb +2 -0
  67. data/lib/aws-sdk-s3/object_summary.rb +2 -0
  68. data/lib/aws-sdk-s3/object_version.rb +2 -0
  69. data/lib/aws-sdk-s3/plugins/accelerate.rb +2 -0
  70. data/lib/aws-sdk-s3/plugins/bucket_arn.rb +2 -0
  71. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +2 -0
  72. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +2 -0
  73. data/lib/aws-sdk-s3/plugins/dualstack.rb +2 -0
  74. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +2 -0
  75. data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +2 -0
  76. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +2 -0
  77. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +2 -0
  78. data/lib/aws-sdk-s3/plugins/location_constraint.rb +2 -0
  79. data/lib/aws-sdk-s3/plugins/md5s.rb +23 -24
  80. data/lib/aws-sdk-s3/plugins/redirects.rb +2 -0
  81. data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
  82. data/lib/aws-sdk-s3/plugins/s3_signer.rb +2 -0
  83. data/lib/aws-sdk-s3/plugins/sse_cpk.rb +2 -0
  84. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -0
  85. data/lib/aws-sdk-s3/presigned_post.rb +2 -0
  86. data/lib/aws-sdk-s3/presigner.rb +2 -0
  87. data/lib/aws-sdk-s3/resource.rb +2 -0
  88. data/lib/aws-sdk-s3/types.rb +2 -0
  89. data/lib/aws-sdk-s3/waiters.rb +2 -0
  90. metadata +18 -4
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -8,7 +10,8 @@ module Aws
8
10
  # @param [IO#write] io An IO-like object that responds to `#write`.
9
11
  def initialize(cipher, io)
10
12
  @cipher = cipher.clone
11
- @io = io
13
+ # Ensure that IO is reset between retries
14
+ @io = io.tap { |io| io.truncate(0) if io.respond_to?(:truncate) }
12
15
  end
13
16
 
14
17
  # @return [#write]
@@ -23,6 +26,10 @@ module Aws
23
26
  @io.write(@cipher.final)
24
27
  end
25
28
 
29
+ def size
30
+ @io.size
31
+ end
32
+
26
33
  end
27
34
  end
28
35
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stringio'
2
4
  require 'tempfile'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Encryption
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Aws
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
 
3
5
  module Aws
@@ -0,0 +1,388 @@
1
+ require 'forwardable'
2
+
3
+ module Aws
4
+ module S3
5
+
6
+ # Provides an encryption client that encrypts and decrypts data client-side,
7
+ # storing the encrypted data in Amazon S3. The EncryptionV2::Client
8
+ # provides improved security over the Encryption::Client by using more
9
+ # modern and secure algorithms. The EncryptionV2::Client maintains
10
+ # backwards compatibility: Using the EncryptionV2::Client you can decrypt
11
+ # objects encrypted with the Encryption::Client. However, objects you
12
+ # encrypt using the EncryptionV2::Client cannot be decrypted using the
13
+ # Encryption::Client.
14
+ #
15
+ # This client uses a process called "envelope encryption". Your private
16
+ # encryption keys and your data's plain-text are **never** sent to
17
+ # Amazon S3. **If you lose you encryption keys, you will not be able to
18
+ # decrypt your data.**
19
+ #
20
+ # ## Envelope Encryption Overview
21
+ #
22
+ # The goal of envelope encryption is to combine the performance of
23
+ # fast symmetric encryption while maintaining the secure key management
24
+ # that asymmetric keys provide.
25
+ #
26
+ # A one-time-use symmetric key (envelope key) is generated client-side.
27
+ # This is used to encrypt the data client-side. This key is then
28
+ # encrypted by your master key and stored alongside your data in Amazon
29
+ # S3.
30
+ #
31
+ # When accessing your encrypted data with the encryption client,
32
+ # the encrypted envelope key is retrieved and decrypted client-side
33
+ # with your master key. The envelope key is then used to decrypt the
34
+ # data client-side.
35
+ #
36
+ # One of the benefits of envelope encryption is that if your master key
37
+ # is compromised, you have the option of just re-encrypting the stored
38
+ # envelope symmetric keys, instead of re-encrypting all of the
39
+ # data in your account.
40
+ #
41
+ # ## Basic Usage
42
+ #
43
+ # The encryption client requires an {Aws::S3::Client}. If you do not
44
+ # provide a `:client`, then a client will be constructed for you.
45
+ #
46
+ # require 'openssl'
47
+ # key = OpenSSL::PKey::RSA.new(1024)
48
+ #
49
+ # # encryption client
50
+ # s3 = Aws::S3::EncryptionV2::Client.new(encryption_key: key)
51
+ #
52
+ # # round-trip an object, encrypted/decrypted locally
53
+ # s3.put_object(bucket:'aws-sdk', key:'secret', body:'handshake')
54
+ # s3.get_object(bucket:'aws-sdk', key:'secret').body.read
55
+ # #=> 'handshake'
56
+ #
57
+ # # reading encrypted object without the encryption client
58
+ # # results in the getting the cipher text
59
+ # Aws::S3::Client.new.get_object(bucket:'aws-sdk', key:'secret').body.read
60
+ # #=> "... cipher text ..."
61
+ #
62
+ # ## Keys
63
+ #
64
+ # For client-side encryption to work, you must provide one of the following:
65
+ #
66
+ # * An encryption key
67
+ # * A {KeyProvider}
68
+ # * A KMS encryption key id
69
+ #
70
+ # ### An Encryption Key
71
+ #
72
+ # You can pass a single encryption key. This is used as a master key
73
+ # encrypting and decrypting all object keys.
74
+ #
75
+ # key = OpenSSL::Cipher.new("AES-256-ECB").random_key # symmetric key
76
+ # key = OpenSSL::PKey::RSA.new(1024) # asymmetric key pair
77
+ #
78
+ # s3 = Aws::S3::EncryptionV2::Client.new(encryption_key: key)
79
+ #
80
+ # ### Key Provider
81
+ #
82
+ # Alternatively, you can use a {KeyProvider}. A key provider makes
83
+ # it easy to work with multiple keys and simplifies key rotation.
84
+ #
85
+ # ### KMS Encryption Key Id
86
+ #
87
+ # If you pass the id to an AWS Key Management Service (KMS) key,
88
+ # then KMS will be used to generate, encrypt and decrypt object keys.
89
+ #
90
+ # # keep track of the kms key id
91
+ # kms = Aws::KMS::Client.new
92
+ # key_id = kms.create_key.key_metadata.key_id
93
+ #
94
+ # Aws::S3::EncryptionV2::Client.new(
95
+ # kms_key_id: key_id,
96
+ # kms_client: kms,
97
+ # )
98
+ #
99
+ # ## Custom Key Providers
100
+ #
101
+ # A {KeyProvider} is any object that responds to:
102
+ #
103
+ # * `#encryption_materials`
104
+ # * `#key_for(materials_description)`
105
+ #
106
+ # Here is a trivial implementation of an in-memory key provider.
107
+ # This is provided as a demonstration of the key provider interface,
108
+ # and should not be used in production:
109
+ #
110
+ # class KeyProvider
111
+ #
112
+ # def initialize(default_key_name, keys)
113
+ # @keys = keys
114
+ # @encryption_materials = Aws::S3::EncryptionV2::Materials.new(
115
+ # key: @keys[default_key_name],
116
+ # description: JSON.dump(key: default_key_name),
117
+ # )
118
+ # end
119
+ #
120
+ # attr_reader :encryption_materials
121
+ #
122
+ # def key_for(matdesc)
123
+ # key_name = JSON.load(matdesc)['key']
124
+ # if key = @keys[key_name]
125
+ # key
126
+ # else
127
+ # raise "encryption key not found for: #{matdesc.inspect}"
128
+ # end
129
+ # end
130
+ # end
131
+ #
132
+ # Given the above key provider, you can create an encryption client that
133
+ # chooses the key to use based on the materials description stored with
134
+ # the encrypted object. This makes it possible to use multiple keys
135
+ # and simplifies key rotation.
136
+ #
137
+ # # uses "new-key" for encrypting objects, uses either for decrypting
138
+ # keys = KeyProvider.new('new-key', {
139
+ # "old-key" => Base64.decode64("kM5UVbhE/4rtMZJfsadYEdm2vaKFsmV2f5+URSeUCV4="),
140
+ # "new-key" => Base64.decode64("w1WLio3agRWRTSJK/Ouh8NHoqRQ6fn5WbSXDTHjXMSo="),
141
+ # }),
142
+ #
143
+ # # chooses the key based on the materials description stored
144
+ # # with the encrypted object
145
+ # s3 = Aws::S3::EncryptionV2::Client.new(key_provider: keys)
146
+ #
147
+ # ## Materials Description
148
+ #
149
+ # A materials description is JSON document string that is stored
150
+ # in the metadata (or instruction file) of an encrypted object.
151
+ # The {DefaultKeyProvider} uses the empty JSON document `"{}"`.
152
+ #
153
+ # When building a key provider, you are free to store whatever
154
+ # information you need to identify the master key that was used
155
+ # to encrypt the object.
156
+ #
157
+ # ## Envelope Location
158
+ #
159
+ # By default, the encryption client store the encryption envelope
160
+ # with the object, as metadata. You can choose to have the envelope
161
+ # stored in a separate "instruction file". An instruction file
162
+ # is an object, with the key of the encrypted object, suffixed with
163
+ # `".instruction"`.
164
+ #
165
+ # Specify the `:envelope_location` option as `:instruction_file` to
166
+ # use an instruction file for storing the envelope.
167
+ #
168
+ # # default behavior
169
+ # s3 = Aws::S3::EncryptionV2::Client.new(
170
+ # key_provider: ...,
171
+ # envelope_location: :metadata,
172
+ # )
173
+ #
174
+ # # store envelope in a separate object
175
+ # s3 = Aws::S3::EncryptionV2::Client.new(
176
+ # key_provider: ...,
177
+ # envelope_location: :instruction_file,
178
+ # instruction_file_suffix: '.instruction' # default
179
+ # )
180
+ #
181
+ # When using an instruction file, multiple requests are made when
182
+ # putting and getting the object. **This may cause issues if you are
183
+ # issuing concurrent PUT and GET requests to an encrypted object.**
184
+ #
185
+ module EncryptionV2
186
+ class Client
187
+
188
+ extend Deprecations
189
+ extend Forwardable
190
+ def_delegators :@client, :config, :delete_object, :head_object, :build_request
191
+
192
+ # Creates a new encryption client. You must provide one of the following
193
+ # options:
194
+ #
195
+ # * `:encryption_key`
196
+ # * `:kms_key_id`
197
+ # * `:key_provider`
198
+ #
199
+ # You may also pass any other options accepted by `Client#initialize`.
200
+ #
201
+ # @option options [S3::Client] :client A basic S3 client that is used
202
+ # to make api calls. If a `:client` is not provided, a new {S3::Client}
203
+ # will be constructed.
204
+ #
205
+ # @option options [OpenSSL::PKey::RSA, String] :encryption_key The master
206
+ # key to use for encrypting/decrypting all objects.
207
+ #
208
+ # @option options [String] :kms_key_id When you provide a `:kms_key_id`,
209
+ # then AWS Key Management Service (KMS) will be used to manage the
210
+ # object encryption keys. By default a {KMS::Client} will be
211
+ # constructed for KMS API calls. Alternatively, you can provide
212
+ # your own via `:kms_client`.
213
+ #
214
+ # @option options [#key_for] :key_provider Any object that responds
215
+ # to `#key_for`. This method should accept a materials description
216
+ # JSON document string and return return an encryption key.
217
+ #
218
+ # @option options [Symbol] :envelope_location (:metadata) Where to
219
+ # store the envelope encryption keys. By default, the envelope is
220
+ # stored with the encrypted object. If you pass `:instruction_file`,
221
+ # then the envelope is stored in a separate object in Amazon S3.
222
+ #
223
+ # @option options [String] :instruction_file_suffix ('.instruction')
224
+ # When `:envelope_location` is `:instruction_file` then the
225
+ # instruction file uses the object key with this suffix appended.
226
+ #
227
+ # @option options [KMS::Client] :kms_client A default {KMS::Client}
228
+ # is constructed when using KMS to manage encryption keys.
229
+ #
230
+ def initialize(options = {})
231
+ @client = extract_client(options)
232
+ @cipher_provider = cipher_provider(options)
233
+ @envelope_location = extract_location(options)
234
+ @instruction_file_suffix = extract_suffix(options)
235
+ end
236
+
237
+ # @return [S3::Client]
238
+ attr_reader :client
239
+
240
+ # @return [KeyProvider, nil] Returns `nil` if you are using
241
+ # AWS Key Management Service (KMS).
242
+ attr_reader :key_provider
243
+
244
+ # @return [Symbol<:metadata, :instruction_file>]
245
+ attr_reader :envelope_location
246
+
247
+ # @return [String] When {#envelope_location} is `:instruction_file`,
248
+ # the envelope is stored in the object with the object key suffixed
249
+ # by this string.
250
+ attr_reader :instruction_file_suffix
251
+
252
+ # Uploads an object to Amazon S3, encrypting data client-side.
253
+ # See {S3::Client#put_object} for documentation on accepted
254
+ # request parameters.
255
+ # @option params [Hash] :kms_encryption_context Additional encryption
256
+ # context to use with KMS. Applies only when KMS is used. In order
257
+ # to decrypt the object you will need to provide the identical
258
+ # :kms_encryption_context to `get_object`.
259
+ # @option (see S3::Client#put_object)
260
+ # @return (see S3::Client#put_object)
261
+ # @see S3::Client#put_object
262
+ def put_object(params = {})
263
+ kms_encryption_context = params.delete(:kms_encryption_context)
264
+ req = @client.build_request(:put_object, params)
265
+ req.handlers.add(EncryptHandler, priority: 95)
266
+ req.context[:encryption] = {
267
+ cipher_provider: @cipher_provider,
268
+ envelope_location: @envelope_location,
269
+ instruction_file_suffix: @instruction_file_suffix,
270
+ kms_encryption_context: kms_encryption_context
271
+ }
272
+ req.send_request
273
+ end
274
+
275
+ # Gets an object from Amazon S3, decrypting data locally.
276
+ # See {S3::Client#get_object} for documentation on accepted
277
+ # request parameters.
278
+ # @option params [String] :instruction_file_suffix The suffix
279
+ # used to find the instruction file containing the encryption
280
+ # envelope. You should not set this option when the envelope
281
+ # is stored in the object metadata. Defaults to
282
+ # {#instruction_file_suffix}.
283
+ # @option params [Hash] :kms_encryption_context Additional encryption
284
+ # context to use with KMS. Applies only when KMS is used.
285
+ # @option params [String] :instruction_file_suffix
286
+ # @option (see S3::Client#get_object)
287
+ # @return (see S3::Client#get_object)
288
+ # @see S3::Client#get_object
289
+ # @note The `:range` request parameter is not yet supported.
290
+ def get_object(params = {}, &block)
291
+ if params[:range]
292
+ raise NotImplementedError, '#get_object with :range not supported'
293
+ end
294
+ envelope_location, instruction_file_suffix = envelope_options(params)
295
+ kms_encryption_context = params.delete(:kms_encryption_context)
296
+ req = @client.build_request(:get_object, params)
297
+ req.handlers.add(DecryptHandler)
298
+ req.context[:encryption] = {
299
+ cipher_provider: @cipher_provider,
300
+ envelope_location: envelope_location,
301
+ instruction_file_suffix: instruction_file_suffix,
302
+ kms_encryption_context: kms_encryption_context
303
+ }
304
+ req.send_request(target: block)
305
+ end
306
+
307
+ private
308
+
309
+ def extract_client(options)
310
+ options[:client] || begin
311
+ options = options.dup
312
+ options.delete(:kms_key_id)
313
+ options.delete(:kms_client)
314
+ options.delete(:key_provider)
315
+ options.delete(:encryption_key)
316
+ options.delete(:envelope_location)
317
+ options.delete(:instruction_file_suffix)
318
+ S3::Client.new(options)
319
+ end
320
+ end
321
+
322
+ def kms_client(options)
323
+ options[:kms_client] || begin
324
+ KMS::Client.new(
325
+ region: @client.config.region,
326
+ credentials: @client.config.credentials,
327
+ )
328
+ end
329
+ end
330
+
331
+ def cipher_provider(options)
332
+ if options[:kms_key_id]
333
+ KmsCipherProvider.new(
334
+ kms_key_id: options[:kms_key_id],
335
+ kms_client: kms_client(options),
336
+ )
337
+ else
338
+ @key_provider = extract_key_provider(options)
339
+ DefaultCipherProvider.new(key_provider: @key_provider)
340
+ end
341
+ end
342
+
343
+ def extract_key_provider(options)
344
+ if options[:key_provider]
345
+ options[:key_provider]
346
+ elsif options[:encryption_key]
347
+ DefaultKeyProvider.new(options)
348
+ else
349
+ msg = 'you must pass a :kms_key_id, :key_provider, or :encryption_key'
350
+ raise ArgumentError, msg
351
+ end
352
+ end
353
+
354
+ def envelope_options(params)
355
+ location = params.delete(:envelope_location) || @envelope_location
356
+ suffix = params.delete(:instruction_file_suffix)
357
+ if suffix
358
+ [:instruction_file, suffix]
359
+ else
360
+ [location, @instruction_file_suffix]
361
+ end
362
+ end
363
+
364
+ def extract_location(options)
365
+ location = options[:envelope_location] || :metadata
366
+ if [:metadata, :instruction_file].include?(location)
367
+ location
368
+ else
369
+ msg = ':envelope_location must be :metadata or :instruction_file '\
370
+ "got #{location.inspect}"
371
+ raise ArgumentError, msg
372
+ end
373
+ end
374
+
375
+ def extract_suffix(options)
376
+ suffix = options[:instruction_file_suffix] || '.instruction'
377
+ if suffix.is_a? String
378
+ suffix
379
+ else
380
+ msg = ':instruction_file_suffix must be a String'
381
+ raise ArgumentError, msg
382
+ end
383
+ end
384
+
385
+ end
386
+ end
387
+ end
388
+ end