cloud_powers 1.0.1 → 1.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.
@@ -0,0 +1,217 @@
1
+ require 'cloud_powers/helpers'
2
+ require 'cloud_powers/storage'
3
+
4
+ module Smash
5
+ module CloudPowers
6
+ module Storage
7
+ class Bucket < Smash::CloudPowers::Resource
8
+ include Smash::CloudPowers::Storage
9
+
10
+ attr_accessor :origin, :delimiter, :tie_in_path
11
+
12
+ def initialize(name:, client: s3, **config)
13
+ super
14
+ @bucket = Aws::S3::Bucket.new(name: name, client: client)
15
+ build_storage(name: 'local', named_type: 'storage').link
16
+ @delimiter = config[:delimiter] || common_delimiter
17
+ # should be the 'lowest level' of storage this object is aware of
18
+ @origin = config[:origin] || project_root.split.last
19
+ @tie_in_path = paths_gcd(local_storage.tie_in_path, project_root)
20
+ end
21
+
22
+ def self.tie_in_config
23
+ # TODO make this able to use #to_snake so it can be dynamic
24
+ { name: 'bucket', client: 's3' }
25
+ end
26
+
27
+ # def bucket_find(pattern, **config)
28
+ # bucket_select(pattern).first
29
+ # end
30
+
31
+ # def bucket_select(pattern, **config)
32
+ # client.list_buckets.buckets.select { |b| %r"#{name}" =~ b.name }
33
+ # end
34
+
35
+ def create_resource
36
+ @response = s3.create_bucket(bucket: name)
37
+ yield self if block_given?
38
+ self
39
+ end
40
+
41
+ def exists?
42
+ # TODO nil.exists? will asplode
43
+ !!(@bucket.exists? rescue nil)
44
+ end
45
+
46
+ # Find out if a job exists in the bucket
47
+ #
48
+ # Parameters
49
+ # * file_name +Pathname+|+String+ - the name of the file you want to pass
50
+ # * location +Pathname+|+String+ (optional) - this helps speed up the
51
+ # process by limiting any searching for the file
52
+ #
53
+ # Returns
54
+ # +IO+ -
55
+ def job_file_exists?(file_name, location = job_path)
56
+ normalized_location = paths_gcd(project_root, location)
57
+ !get_file_names(file_name, location: normalized_location).empty?
58
+ end
59
+
60
+ def link
61
+ if exists?
62
+ # do stuff
63
+ else
64
+ save!
65
+ end
66
+
67
+ @linked = @response.nil?
68
+ end
69
+
70
+ def select(*args, **opts)
71
+ pattern = args.pop
72
+ location = args.shift || origin
73
+
74
+ names = if filename?(pattern)
75
+ get_file_names(pattern, location: location)
76
+ else
77
+ get_bucket_names(pattern, location: location)
78
+ end
79
+
80
+
81
+ names = get_file_names(pattern, location: location)
82
+ if block_given?
83
+ names.map do |file_name|
84
+ new_file = to_realpath(local_storage.find(file_name))
85
+ File.open(new_file, 'r+') do |file|
86
+ data = get_file(file_name, location: location).get.body.read
87
+ yield file, data
88
+ end
89
+ end
90
+ else
91
+ names
92
+ end
93
+ end
94
+
95
+ def find(*args, location: origin, **opts)
96
+ path = select(*args, location: location, **opts).first
97
+ file_name = path.kind_of?(Pathname) ? path.split.last.to_s : path
98
+ file_permissions = opts[:file_permissions] || 'a+'
99
+ path = local_storage.tie_in_path + path
100
+
101
+ if block_given?
102
+ begin
103
+ File.open(path, file_permissions) do |file|
104
+ data = get_file(file_name, location: location).get.body.read
105
+ yield file, data
106
+ end
107
+ rescue Exception => e
108
+ if e =~ /no implicit conversion of nil into String/
109
+ logger.info "no file found from #{path} called #{file}"
110
+ nil
111
+ else
112
+ super
113
+ end
114
+ end
115
+ else
116
+ path
117
+ end
118
+ end
119
+
120
+ # Notes:
121
+ # * See https://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.html#put_object-instance_method
122
+ def put(file_name, location = origin, **config)
123
+ body = get_local_file_body(file_name, location)
124
+ return if body.nil?
125
+
126
+ client.put_object(
127
+ put_config(body: body, key: file_name, **config)
128
+ )#.on_success { Thread.new { update_registry } }
129
+ true
130
+ end
131
+
132
+ private
133
+ # Get a local file using the file name and optional location
134
+ #
135
+ # Parameters
136
+ # * +Pathname+ - absolute path is preferable but a search for the file
137
+ # is initiated if it isn't found at <tt>local_storage.origin</tt>
138
+ #
139
+ # Returns
140
+ # +String+|+IO+
141
+ def get_local_file_body(file, location = origin)
142
+ scope = paths_gcd(tie_in_path, location)
143
+ local_storage.
144
+ find(file, location: scope, file_rights: 'r') { |data| data }
145
+ end
146
+
147
+ # Get object names from repeated
148
+ # <tt>Aws::S3::Client#list_objects_v2</tt> calls.
149
+ #
150
+ # Parameters
151
+ # * pattern +Regex+|+String+(glob) - the name of the object you are
152
+ # looking for
153
+ # * config +KeywordArgument+(s) - override options for the request to
154
+ # <tt>Aws::S3::Client.list_objects_v2</tt>
155
+ #
156
+ # Returns
157
+ # * +Array+ of Aws responses
158
+ #
159
+ # Notes:
160
+ # * See https://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.html#list_objects_v2-instance_method
161
+ # * See https://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Bucket.html#objects-instance_method
162
+ def get_file_names(pattern, location: origin)
163
+ objects = @bucket.objects(
164
+ delimiter: delimiter,
165
+ encoding_type: "url",
166
+ marker: "Marker",
167
+ prefix: location.to_s,
168
+ ).map(&:key).grep(pattern)
169
+ end
170
+
171
+ def get_bucket_names(pattern, location: origin)
172
+ buckets = client.list_buckets.buckets.map(&:name)
173
+ end
174
+
175
+ # Get the actual object using S3::Bucket
176
+ def get_file(file_name, location: origin)
177
+ @bucket.object(file_name)
178
+ end
179
+
180
+ # Tweak the config
181
+ def put_config(**config)
182
+ {
183
+ acl: config[:acl] || "private", # accepts private, public-read, public-read-write, authenticated-read, aws-exec-read, bucket-owner-read, bucket-owner-full-control
184
+ body: config[:body], # file/IO object, or string data
185
+ bucket: name, # required
186
+ # cache_control: "CacheControl",
187
+ # content_disposition: "ContentDisposition",
188
+ # content_encoding: "ContentEncoding",
189
+ # content_language: "ContentLanguage",
190
+ # content_length: 1,
191
+ # content_md5: "ContentMD5",
192
+ # content_type: "ContentType",
193
+ # expires: Time.now,
194
+ # grant_full_control: "GrantFullControl",
195
+ # grant_read: "GrantRead",
196
+ # grant_read_acp: "GrantReadACP",
197
+ # grant_write_acp: "GrantWriteACP",
198
+ key: config[:file_name], # required
199
+ # metadata: {
200
+ # "MetadataKey" => "MetadataValue",
201
+ # },
202
+ server_side_encryption: "AES256", # accepts AES256, aws:kms
203
+ storage_class: "STANDARD", # accepts STANDARD, REDUCED_REDUNDANCY, STANDARD_IA
204
+ # website_redirect_location: "WebsiteRedirectLocation",
205
+ # sse_customer_algorithm: "SSECustomerAlgorithm",
206
+ # sse_customer_key: "SSECustomerKey",
207
+ # sse_customer_key_md5: "SSECustomerKeyMD5",
208
+ # ssekms_key_id: "SSEKMSKeyId",
209
+ # request_payer: "requester", # accepts requester
210
+ tagging: "TaggingHeader", # !! Relate this to the project !!
211
+ use_accelerate_endpoint: false,
212
+ }.merge(config)
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,129 @@
1
+ require 'cloud_powers/storage'
2
+
3
+ module Smash
4
+ module CloudPowers
5
+ module Storage
6
+ class Local < Smash::CloudPowers::Resource
7
+ include Smash::CloudPowers::Storage
8
+ include Smash::CloudPowers::Zenv
9
+
10
+ attr_accessor :origin, :tie_in_path
11
+
12
+ def initialize(name:, origin: zlib_home, **config)
13
+ super
14
+ @origin = origin.kind_of?(Pathname) ? origin : to_pathname(origin)
15
+ @tie_in_path = to_pathname(@origin, to_camel(name))
16
+ end
17
+
18
+ def self.tie_in_config
19
+ # TODO make this able to use #to_snake so it can be dynamic
20
+ { type: 'local', origin: 'zlib_path' }
21
+ end
22
+
23
+ # def self.config(*opts)
24
+ # {
25
+ # name: opts.select { |k| k.kind_of? String }.first || 'local',
26
+ # origin: opts.select { |k| k.kind_of? Pathname }.first,
27
+ # config: opts.select { |k| k.kind_of? Hash }.first
28
+ # }
29
+ # end
30
+
31
+ # Creates an actual directory or file using the standard format for
32
+ # <b><i>this</i></b> storage name (camel case)
33
+ #
34
+ # Returns
35
+ # <tt>Storage::Local</tt?
36
+ def create_resource
37
+ @response = to_realpath(tie_in_path)
38
+
39
+ yield self if block_given?
40
+ self
41
+ end
42
+
43
+ # Find out if this local directory exists. Because directories are one
44
+ # of the ways to scope in <tt>CloudPowers::Storage::Local</tt>, we can
45
+ # find out if this local object is already on disk by searching the
46
+ # system, up to and including the root directory. The search attempts
47
+ # to first search through a smaller scope by checking in the
48
+ # +project_root+, +origin+ and +./zlib+ directories before giving up
49
+ # and searching the whole machine from +/+ or +C:\+ etc
50
+ #
51
+ # Parameters
52
+ # * scope +Pathname+ - default is +project_root+|+origin+|+zlib+|+Root Dir+
53
+ #
54
+ # Returns
55
+ # * +Boolean+
56
+ #
57
+ # Notes:
58
+ # * See <tt>#initialize</tt> for +origin+
59
+ def exists?(scope: origin.parent)
60
+ search_results = path_search(name, scope)
61
+ !(search_results.empty?)
62
+ end
63
+
64
+ # Tie the object on disk with this object, in memory. If the object
65
+ # on disk doesn't exist, it will be created now.
66
+ #
67
+ # Returns
68
+ # * <tt>Smash::CloudPowers::Storage::Local</tt> - +self+
69
+ def link
70
+ @linked = exists? ? true : save!
71
+ self
72
+ end
73
+
74
+ def job_file_exists?(object_name = '')
75
+ !!(find(object_name, location: job_home))
76
+ end
77
+
78
+ def select(*patterns, **opts)
79
+ file_rights = opts[:file_rights] || 'a+'
80
+ scope = paths_lcd(
81
+ tie_in_path, to_pathname(opts[:location]).expand_path
82
+ )
83
+
84
+ regexs = patterns.map do |pattern|
85
+ if pattern.kind_of?(Pathname)
86
+ additional_scope, basename = pattern.split
87
+ scope = paths_lcd(additional_scope, scope)
88
+ %r"#{basename}$"
89
+ else
90
+ pattern.kind_of?(Regexp) ? pattern : %r"#{pattern.to_s}$"
91
+ end
92
+ end
93
+
94
+ paths = regexs.map do |regex|
95
+ Dir["#{scope}/**/*"].
96
+ grep(regex).
97
+ map { |path| to_realpath(path) }
98
+ end.flatten
99
+
100
+ if block_given?
101
+ paths.map { |path| yield File.open(path, file_rights) }
102
+ else
103
+ paths
104
+ end.flatten
105
+ end
106
+
107
+ def find(*args, location: origin, **opts)
108
+ # TODO:
109
+ # it's good to use select but this should use a stronger search or
110
+ # something, to provide something more than just `.first`-ing an array
111
+ path = select(*args, location: location, **opts).first
112
+ file_rights = opts[:file_rights] || 'a+'
113
+ if block_given?
114
+ begin
115
+ File.open(path, file_rights) do |file|
116
+ yield file.read
117
+ end
118
+ rescue TypeError => e
119
+ logger.info("file doesn't exist #{path}")
120
+ nil
121
+ end
122
+ else
123
+ path
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -1,16 +1,85 @@
1
1
  require 'pathname'
2
- require_relative 'aws_resources'
2
+ require 'cloud_powers/aws_resources'
3
+ require 'cloud_powers/helpers'
4
+ require 'cloud_powers/storage/bucket'
5
+ require 'cloud_powers/storage/local'
3
6
 
4
7
  module Smash
5
8
  module CloudPowers
6
9
  module Storage
7
10
  include Smash::CloudPowers::AwsResources
11
+ include Smash::CloudPowers::Helpers
12
+
13
+ def existing_storage(types = all_storage_types)
14
+ normalized_types = types.map { |type| to_pascal(type) }
15
+ i_vars.select do |k,v|
16
+ normalized_types.include?(to_pascal(v.type)) rescue nil
17
+ end.values
18
+ end
19
+
20
+ # Get all existing Storage. If there isn't any existing Storage objects,
21
+ # get some delegates for the available types. If parameters are passed,
22
+ # narrow down the result set with the params.
23
+ #
24
+ # Parameters
25
+ # * queries +String+|+Symbol+|+Storage+ - a
26
+ # * +String+ will represent a name and gets searched for using
27
+ # delegates. If a location is found with no Storage object that
28
+ # matches, it builds it/them.
29
+ # * +Symbol+s are used to narrow down the types used in the search.
30
+ # * +Storage+ This method also allows you to pass in +Storage+ objects
31
+ # and have them passed into the list of returned +Storage+ objects.
32
+ # This is useful for allowing this method to be used in other methods
33
+ #
34
+ # Returns
35
+ # * +Array+ of <tt>Smash::CloudPowers::Storage[::?]</tt>
36
+
37
+ def all_storage(queries = [])
38
+ types, passed, names = sort_all_storage_params(queries)
39
+ # combine the search efforts from above
40
+ found = existing_storage(types) + passed
41
+ found_names = found.map(&:name)
42
+
43
+ # make sure all the names that were not found are represented as a new
44
+ # Storage object, if it isn't already
45
+ unmatched_names = names - found_names
46
+
47
+ # if all the names are represented, return the correct results
48
+ if unmatched_names.empty?
49
+ (found + storage_delegates(types)).uniq
50
+ else
51
+ # or build ones that should be and add it to the results, then return
52
+ # them all.
53
+ storage_delegates(types).map do |storage|
54
+ unlinked_storages = storage.select(*unmatched_names).reject do |n|
55
+ filename? n
56
+ end
57
+
58
+ unlinked_storages.map do |unlinked_storage|
59
+ # TODO create a method in path_help to separate. it can be used
60
+ # here and instead of doing it in to_snake, to_snake can use it
61
+ # too
62
+ clues = unlinked_storage.to_s.split(%r"#{common_delimiter}")
63
+
64
+ config = {
65
+ name: clues.pop,
66
+ type: storage.type,
67
+ origin: clues.join(common_delimiter)
68
+ }
69
+
70
+ config = storage_config(storage.type).merge(config)
71
+ build_storage(**config)
72
+ end
73
+ end.flatten
74
+ end
75
+ end
8
76
 
9
77
  def local_job_file_exists?(file)
10
- File.exists?(job_path(to_ruby_file_name(file)))
78
+ path = job_path(to_ruby_file_name(file))
79
+ storage_select(file, location: [:local, path]).count > 0
11
80
  end
12
81
 
13
- # Searches a local job storage location for the given +file+ name
82
+ # Searches a local jobs storage location for the given +file+ name
14
83
  # if it exists - exit the method
15
84
  # if it does <i>not</i> exist - get the file from s3 and place it in
16
85
  # the directory that was just searched bucket using +#zfind()+
@@ -43,17 +112,22 @@ module Smash
43
112
  # | |_foobar.rb
44
113
  # | |_custom_greetings.js # could be an after effects JS script
45
114
  def source_job(file)
46
- # TODO: better path management
47
- bucket = zfind('job storage')
48
- if job_path(to_ruby_file_name(file)).nil?
49
- objects = s3.list_objects(bucket: bucket).contents.select do |f|
50
- /#{Regexp.escape file}/i =~ f.key
115
+ storage_delegates(:bucket).map do |delegate|
116
+ delegate.find(file, location: zfind('jobs storage')) do |f, data|
117
+ f.write data
51
118
  end
119
+ end.flatten
120
+ # TODO: better path management
121
+ # bucket = zfind('jobs storage')
122
+ # if job_path(to_ruby_file_name(file)).nil?
123
+ # objects = s3.list_objects(bucket: bucket).contents.select do |f|
124
+ # /#{Regexp.escape file}/i =~ f.key
125
+ # end
52
126
 
53
- objects.each do |obj|
54
- s3.get_object(bucket: bucket, key: obj.key, response_target: job_path(file))
55
- end
56
- end
127
+ # objects.each do |obj|
128
+ # s3.get_object(bucket: bucket, key: obj.key, response_target: job_path(file))
129
+ # end
130
+ # end
57
131
  end
58
132
 
59
133
  # Search through a bucket to find a file, based on a regex
@@ -69,6 +143,8 @@ module Smash
69
143
  # matches.first.contents.size
70
144
  # # => 238934 # integer representation of the file size
71
145
  def search(bucket, pattern)
146
+ # this guard allows methods that return nil to be used as the 'bucket',
147
+ # like #zfind()
72
148
  return [] if bucket.nil?
73
149
 
74
150
  begin
@@ -82,6 +158,69 @@ module Smash
82
158
  end
83
159
  end
84
160
 
161
+ # This method builds a <tt>CloudPowers::Storage::</tt> object for you to
162
+ # use but doesn't invoke the <tt>#create!()</tt> method, so no API call is
163
+ # made, no directories are created, etc. This can be used even if the
164
+ # storage already exists.
165
+ #
166
+ # Parameters
167
+ # * name +String+ - name of the storage you want to interact with
168
+ #
169
+ # Returns
170
+ # Storage::[Local|Bucket]
171
+ #
172
+ # Example
173
+ # storage_object = build_queue('exampleQueue')
174
+ # storage_object.select('job')
175
+ # => job.rb
176
+ def build_storage(name:, type: nil, **config)
177
+ type = to_pascal(type || :local)
178
+
179
+ storage_resource = Smash::CloudPowers::Storage.const_get(type).
180
+ build(name: to_camel(name), type: (type || :storage), **config)
181
+
182
+ yield storage_resource if block_given?
183
+
184
+ attr_map(storage_resource.call_name => storage_resource) do |attribute, resource|
185
+ instance_attr_accessor attribute
186
+ resource
187
+ end
188
+
189
+ storage_resource
190
+ end
191
+
192
+
193
+ # Create a storage without explicitly creating an object, of whatever type
194
+ # we're working with, here. The types can be of any
195
+ # <tt>CloudPowers::Storage</tt> or any other class, as long as it follows
196
+ # the <tt>CloudPowers::Createable</tt> Interface
197
+ #
198
+ # Parameters
199
+ # * name +String+ - The name of the Queue to be created
200
+ #
201
+ # Returns
202
+ # CloudPowers::Storage::[Local|Bucket]
203
+ #
204
+ # Example
205
+ # create_queue('exampleQueue')
206
+ # get_queue_message_count
207
+ def create_storage(name:, type: nil, **config)
208
+ # default is :local
209
+ type, config[:type] = type.nil? ? [:local, :storage] : [type, type]
210
+
211
+ storage_resource =
212
+ Smash::CloudPowers::Storage.const_get(to_pascal(type)).create!(
213
+ name: to_camel(name), **config
214
+ )
215
+
216
+ attr_map(storage_resource.call_name => storage_resource) do |attribute, resource|
217
+ instance_attr_accessor attribute
218
+ resource
219
+ end
220
+
221
+ storage_resource
222
+ end
223
+
85
224
  # Send the log files to the S3 log file bucket
86
225
  #
87
226
  # Returns
@@ -95,6 +234,139 @@ module Smash
95
234
  )
96
235
  end
97
236
  end
237
+
238
+ # Search through all Storage or just the ones that fit your search
239
+ # parameters. The searching algorithm takes you through existing
240
+ # +Storage+ objects and the locations they represent, like local the file
241
+ # system and AWS S3 Buckets
242
+ #
243
+ # Parameters
244
+ # * list of +String+ - the name(s) of the file, direcotry, bucket, etc you
245
+ # are looking for
246
+ # * list of +KeyValue+ pairs - search options that are used for narrowing
247
+ # down your objects
248
+ # * * +:location+ +:local+|+:bucket+|+String+
249
+ #
250
+ # Returns
251
+ # * Array +Objects+ - can be the return value of whichever Storage object
252
+ # found something that matched the param(s)
253
+ #
254
+ # Notes:
255
+ # * Narrowing down the search results speeds up the search
256
+ # * If a block is passed to this method, it will be run in the
257
+ # <tt>#select</tt> method of the current Storage type
258
+ # * See the <tt>#select</tt> methods for all the Storage classes you are
259
+ # using
260
+ def storage_select(*names, **opts)
261
+ # make sure to process all possible types of names that can come in.
262
+ # symbols are reserved for types, not names.
263
+ locations = [opts.delete(:location) || opts.delete(:locations)].flatten
264
+ locations = (locations + extract!(names) { |n| n.kind_of? Symbol }).uniq
265
+
266
+ all_storage(locations).map do |storage|
267
+ if block_given?
268
+ storage.select(*names, **opts) do |object|
269
+ yield object
270
+ end
271
+ else
272
+ storage.select(*names, **opts)
273
+ end
274
+ end.flatten
275
+ end
276
+
277
+ private
278
+ # Get all types that are both +Storage+ and work with the +Creatable+ ->
279
+ # +Resource+ interface.
280
+ #
281
+ # Parameters
282
+ # * a list of types that both fit the requirements listed in this method's
283
+ # description and are in the list given as parameter(s).
284
+ #
285
+ # Returns
286
+ # * <tt>Array[Symbols]</tt>
287
+ def all_storage_types(*types)
288
+ # TODO - Death to harcoding!!!
289
+ # this should be able to query the Storage module or something to figure
290
+ # out all the storage types instead of hardcoding
291
+ [:local, :bucket] - types
292
+ end
293
+
294
+ # sort out the parameters that can be sent to the <tt>#all_storage</tt>
295
+ # method, to get rid of some of the bulk in that method.
296
+ #
297
+ # Parameters
298
+ # * queries +Array+ of +Symbol+|+String+|+Storage+ - see <tt>all_storage</tt>
299
+ # for a description of the params
300
+ #
301
+ # Returns
302
+ # +Array+ of +Array+ of segregated params
303
+ def sort_all_storage_params(queries)
304
+ # get all types from the queries parameter then normalize them so they
305
+ # can be compared to another array's terms
306
+ types = queries.select { |element| element.kind_of? Symbol }
307
+ types = all_storage_types if types.empty?
308
+ normalized_types = types.map { |element| to_pascal(element) }
309
+
310
+ # get all the Storage objects and pass them through, to the returned
311
+ # Storage objects
312
+ passed = queries.select do |element|
313
+ normalized_types.include?(to_pascal(element.type)) rescue nil
314
+ end
315
+
316
+ # get all the Strings and use them as names of Storage(s)
317
+ names = queries.select { |element| element.kind_of? String }
318
+ [types, passed, names]
319
+ end
320
+
321
+ # Get delegate Storage objects for every type available. A delegate is
322
+ # just a basic Storage object of a given type, that can access enough
323
+ # locations, on its own, that it can be used as a good, base representation
324
+ # of a Storage location, "out in the wild"; e.g. local, cloud, networked
325
+ # etc.
326
+ #
327
+ # Returns
328
+ # * +Array+ of <tt>Smash::CloudPowers::Storage[::?]</tt> - with access to
329
+ # all or most of the "root" of the storage out in the wild
330
+ def storage_delegates(types = all_storage_types)
331
+ types = [types].flatten
332
+ types.map do |type|
333
+ delegate_call_name = "delegate_#{type}"
334
+ if respond_to? delegate_call_name
335
+ public_send delegate_call_name
336
+ else
337
+ config = storage_config(type).merge({ name: 'delegate', type: type })
338
+ # set delegates up to search everywhere
339
+ build_storage(**config) do |storage|
340
+ storage.tie_in_path = storage.origin
341
+ end
342
+ end
343
+ end
344
+ end
345
+
346
+ # Get information directly from the class you will soon instantiate. This
347
+ # method gathers information from the class that the class itself deems
348
+ # important enough to tell you about, for instantiating it. The method
349
+ # will call 0..n methods on your current class to gain information from
350
+ # this context; that it may be used in the new storage class' context as
351
+ # well.
352
+ #
353
+ # The best example of this is the clients
354
+ def storage_config(type)
355
+ tie_in_config = begin
356
+ Smash::CloudPowers.const_get(to_pascal(type)).
357
+ tie_in_config.
358
+ inject({}) do |conf, (attribute, method)|
359
+ conf[attribute] = if self.respond_to? method
360
+ public_send(method)
361
+ else
362
+ method
363
+ end
364
+ conf
365
+ end
366
+ rescue Exception => e
367
+ { name: to_snake(type) }
368
+ end
369
+ end
98
370
  end
99
371
  end
100
372
  end