cloud_powers 0.1.4 → 0.1.5

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: 391860e2ec54145cbb41486fd29c2915caaaf7b5
4
- data.tar.gz: 1bccfe4b9cbb9f3293c2c3a740567edc0e9eb9d5
3
+ metadata.gz: 5276c7479f1dfcdc4d59aaffa8841d5ab4839180
4
+ data.tar.gz: 9dcab0fc49c1c0bd9e13fd259719ef697f209429
5
5
  SHA512:
6
- metadata.gz: 83e75d3065430d6e2123a4b7e7104818c2fbcf9f0a091022c6e85aec929bc7d335bb0abdda6393e8435eb0e9ee533fd0a4eae8d17cfac3be93f7a505de410ae1
7
- data.tar.gz: f1c12786a596e76cd4d87aefff57a8d8d2abf4924a80c8653624c3749c4aec290dbe2cc9e81c4696c813fd7930b6c2faac31f0d50eea5e285b9f76923ff25f47
6
+ metadata.gz: b5c3f428b50af4dd03c40521d461ed4bc7c23c28b598557b3495bfe85ee63a1571d6f863c0d1203ba9a309214344222b073a232cd340e67c6841ed30863ae65c
7
+ data.tar.gz: 6bbac08f878d73345f023fde38ffbde8ab92c53a134c828fe4721d176363126ad99e028672e4c3383837afe935da5b32b2f4bffa73a06506710f6e58fa22f29c
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  *.env
11
+ .byebug_history
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cloud_powers (0.1.4)
4
+ cloud_powers (0.1.5)
5
5
  activesupport-core-ext (~> 4)
6
6
  aws-sdk (~> 2)
7
7
  dotenv (~> 2.1)
data/README.md CHANGED
@@ -1,8 +1,11 @@
1
1
  # CloudPowers
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/cloud_powers`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ## Description
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ CloudPowers is a wrapper around certain AWS services. It was developed with the _need_ instead of the _resource_ in mind so even though AWS is the only service provider included now, it shouldn't be the only one forever. There are several pieces of the Cloud Power module to talk about: SelfAwareness, Synapse, Delegator and more. Each module in Cloud Powers is in charge of a specific type of task that helps bring projects together and communicate with the outside world.
6
+
7
+ Below is a breakdown of the installation, common services provided and an example or 2 of popular methods.
8
+ _Better docs are on the way_
6
9
 
7
10
  ## Installation
8
11
 
@@ -12,7 +15,7 @@ Add this line to your application's Gemfile:
12
15
  gem 'cloud_powers'
13
16
  ```
14
17
 
15
- And then execute:
18
+ then execute:
16
19
 
17
20
  $ bundle
18
21
 
@@ -20,10 +23,140 @@ Or install it yourself as:
20
23
 
21
24
  $ gem install cloud_powers
22
25
 
26
+ then either:
27
+ * set environment variables that matches the below group
28
+ * fill out a .env file and load it from the class that is using CloudPowers, like this
29
+ ```ruby
30
+ require 'dotenv'
31
+ Dotenv.load('path to your .env file')
32
+ ```
33
+ _things you need for pre-v1_:
34
+ ```
35
+ # Aws access:
36
+ AWS_ACCESS_KEY_ID=""
37
+ AWS_SECRET_ACCESS_KEY=""
38
+
39
+ # Aws areas and auth-related locations:
40
+ AWS_REGION=""
41
+
42
+ # Aws Build info:
43
+ AMI_NAME="for Cerebrums to create a node"
44
+
45
+ # Aws kinesis stream names
46
+ STATUS_STREAM="e.g. kinesis stream name"
47
+
48
+ # Aws s3 buckets etc
49
+ TASK_STORAGE="e.g. s3 object name"
50
+
51
+ # Aws sqs queue addresses
52
+ BACKLOG_QUEUE_ADDRESS=""
53
+ WIP_QUEUE_ADDRESS=""
54
+ FINISHED_QUEUE_ADDRESS=""
55
+ ```
56
+
23
57
  ## Usage
24
58
 
25
- TODO: Write usage instructions here
59
+ ### AwsResource
60
+ * AWS resources that should be used by many services, e.g. Delegator _on EC2_ uses S3, EC2 and SQS to gain knowledge about the
61
+ context it will be working in.
62
+
63
+
64
+ ### Delegator
65
+ * Helps a node figure out what task it should be running, where its executables are, gathers it/them and loads them.
66
+ * Lives in the Job and Task instantiation chain
67
+
26
68
 
69
+ ### Helper
70
+ * useful shared methods, like one that turns a string into snake_case
71
+
72
+
73
+ ### SelfAwareness
74
+ * gets and sets info about the instance -> Neuron/Cerebrum/etc)
75
+ ```ruby
76
+ get_awareness!
77
+ ```
78
+ * retrieves and sets all metadata from the EC2 instance and a few other things like the instance hostname (can find the instance IP from here).
79
+ * See EC2 metadata for details on the instance/node metadata that is set.
80
+ * Additionally, the instance public hostname, id and a few others are set using other-than-EC2-metadata methods.
81
+
82
+
83
+ ### SmashError
84
+
85
+
86
+ ### Storage
87
+ * S3
88
+
89
+
90
+ ### Synapse
91
+ * A Synapse is used for communicating, usually between nodes and an external source for status updates but the Synapse can handle any kind of information that needs to be passed.
92
+ * Architecture
93
+ * The Synapse is a communications module that is broken up into separate types of communication, for separate needs.
94
+ * There are 2 modules, soon to be 3, inside the Synapse module:
95
+
96
+ #### Queue
97
+ * like a task list or a message board for asynchronous communication
98
+ * Board <Struct>:
99
+ * interface with Queue config, data, name, etc.
100
+ ```ruby
101
+ default_workflow = Workflow.new
102
+ board = Board.new(default_workflow)
103
+ board.name
104
+ => 'backlog'
105
+ board.address
106
+ => 'http://aws-url/backlog-board-url'
107
+ board.next_board # useful because this is a simple state-machine implementation
108
+ => 'wip'
109
+ ```
110
+ Example usage:
111
+ 1. Give the entire stream name or a symbol or string that is found in the .env for the name of the Queue
112
+ 2. provide a block that will create the record that gets sent to the board
113
+ ```ruby
114
+ poll(board_name <string|symbol>, opts <optional config Hash>) { block }
115
+ ```
116
+ or
117
+ ```ruby
118
+ poll(:backlog) { |msg, stats| Task.new(instance_id, msg) if stats.success? }
119
+ ```
120
+ or
121
+ ```ruby
122
+ poll(:backlog, wait_time: 30, delete: false) do
123
+ edited_message = sitrep.merge(foo: 'bar')
124
+ update = some_method(edited_message)
125
+ end
126
+ ```
127
+ #### Pipe
128
+ * Good for real time information passing, like status updates, results reporting, operations, etc.
129
+ * The Pipe module is for communicating via streams or webhooks. Piping is meant to be a way to communicate status, problems and other general info. There can be very high traffic through the pipe or none at all. Very soon, Cerebrums will be data consumers, to the Java KCL and MultiLangDaemon level, so keeping messages straight is done via partition ID. The partition ID of any message is to identify which node the message is about, and so the instance ID is used in nodes like the Neuron and Cerebrum and other identifiers that are deemed best are used in other projects.
130
+
131
+ Example usage:
132
+ ruby```
133
+ pipe_to(stream_name <string/symbol>) { &block }
134
+ ```
135
+ ruby```
136
+ pipe_to(:status_queue) { sitrep(content: 'workflowComplete') }
137
+ ```
138
+ and for multiple records (KCL)
139
+ ```ruby
140
+ flow_to(stream_name <string/symbol>) { &block }
141
+ ```
142
+ ```ruby
143
+ flow_to(:status_queue) do
144
+ interesting_instances = neurons.map do |neuron|
145
+ return neuron if neuron.workflow.done?
146
+ end
147
+ find_efficient_neurons(interesting_instances) # this gets sent through the Pipe
148
+ end
149
+ ```
150
+
151
+
152
+ #### Memory
153
+ * Allows the nodes to have a collective awareness of each other, the environment they work in and other Jobs (Coming soon...)
154
+
155
+
156
+ ### Workflow
157
+ * The workflow is one of the integral ideas behind the Brain architecture. The workflow describes to us humans what state the Job/Task/etc is currently in and it let's the App(s) know what things it can do from any point in the process.
158
+
159
+
27
160
  ## Development
28
161
 
29
162
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -32,7 +165,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
165
 
33
166
  ## Contributing
34
167
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/cloud_powers.
168
+ Bug reports and pull requests are welcome on GitHub at https://github.com/adam-phillipps/cloud_powers.
36
169
 
37
170
 
38
171
  ## License
data/bin/setup CHANGED
@@ -5,4 +5,16 @@ set -vx
5
5
 
6
6
  bundle install
7
7
 
8
- # Do any other automated setup that you need to do here
8
+ find_or_create_env_file() {
9
+ pushd "../spec"
10
+ cp .test.env.example .test.env
11
+ printf "Testing:\n- Fill out the .test.env file inside the spec/ directory"
12
+ popd
13
+ }
14
+
15
+ printf "Better docs are on the way..."
16
+ printf "In order to use this project, until I get it to v1, you'll need to fill out a .env file and reference it in your project\n \
17
+ After v1, you should just need to have the account at AWS and your creds either in your path or in the .env file because\n \
18
+ the code will interrogate your account to find SQS queue urls, Kinesis stream names etc."
19
+ printf "In order to test this project, until I get it to v1, you'll need to fill out a .env file and reference it in the\n \
20
+ spec/ directory. There should be one with values that might be used by whatever spec you're running."
data/cloud_powers.gemspec CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'cloud_powers/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- # spec.required_ruby_version = '~> 2.3.0'
7
+ spec.required_ruby_version = '~> 2.3.0'
8
8
  spec.name = 'cloud_powers'
9
9
  spec.version = CloudPowers::VERSION
10
10
  spec.author = 'Adam Phillipps'
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
15
15
  This wrapper was developed specifically for the Brain project
16
16
  but can be used in any other ruby project.
17
17
  EOF
18
- spec.homepage = 'https://smashanalytics.atlassian.net/wiki/display/SB/Cloud+Powers'
18
+ spec.homepage = 'https://github.com/adam-phillipps/cloud_powers'
19
19
  spec.license = 'MIT'
20
20
 
21
21
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
@@ -42,36 +42,59 @@ module Smash
42
42
  end
43
43
  end
44
44
 
45
+ # Gets the path from the environment and sets @log_file using the path
46
+ # @returns @log_file path <String>
45
47
  def log_file
46
48
  @log_file ||= env('LOG_FILE')
47
49
  end
48
50
 
51
+ # @returns: An instance of Logger, cached as @logger
49
52
  def logger
50
53
  @logger ||= create_logger
51
54
  end
52
55
 
56
+ # Lets you retry a piece of logic with 1 second sleep in between attempts
57
+ # until another bit of logic does what it's supposed to, kind of like
58
+ # continuing to poll something and doing something when a package is ready
59
+ # to be taken and processed.
60
+ # @params:
61
+ # * [allowed_attempts] or Infinity(default) <Number>: The number of times
62
+ # the loop should be allowed to...well, loop, before a failed retry occurs.
63
+ # * &test <Block>: A predicate method or block of code that is callable
64
+ # is used to test if the block being retried is successful yet.
65
+ # * []
53
66
  # Sample usage:
54
67
  # check_stuff = lambda { |params| return true }
55
- # retry(3, check_stuff(params)) { do_stuff_that_needs_to_be_checked }
56
- def retry(allowed_attempts = Float::Infinity, &test)
68
+ # smart_retry(3, check_stuff(params)) { do_stuff_that_needs_to_be_checked }
69
+ def smart_retry(test, allowed_attempts = Float::INFINITY)
57
70
  result = yield if block_given?
58
71
  tries = 1
59
72
  until test.call(result) || tries >= allowed_attempts
60
73
  result = yield if block_given?
74
+ tries += 1
61
75
  sleep 1
62
76
  end
63
77
  end
64
78
 
79
+ # Gives the path from the project root to lib/tasks[/#{file}.rb]
80
+ # @params:
81
+ # * [file] <String>: name of a file
82
+ # @returns:
83
+ # * path[/file] <String>
84
+ # * If a `file` is given, it will have a '.rb' file extension
85
+ # * If no `file` is given, it will return the `#task_require_path`
65
86
  def task_path(file = '')
66
- # t_p = Pathname(__FILE__).parent.dirname + 'tasks'
67
- if file.empty?
68
- Pathname(__FILE__).parent.dirname + 'tasks'
69
- else
70
- Pathname(__FILE__).parent.dirname + "tasks/#{to_snake(file)}"
71
- end
87
+ return task_require_path if file.empty?
88
+ Pathname(__FILE__).parent.dirname + 'tasks' + to_ruby_file_name(file)
72
89
  end
73
90
 
74
- def task_require_path(file_name)
91
+ # Gives the path from the project root to lib/tasks[/file]
92
+ # @params:
93
+ # * [file] <String>: name of a file
94
+ # @returns:
95
+ # * path[/file] <String>
96
+ # * Neither path nor file will have a file extension
97
+ def task_require_path(file_name = '')
75
98
  file = File.basename(file_name, File.extname(file_name))
76
99
  Pathname(__FILE__).parent.dirname + 'tasks' + file
77
100
  end
@@ -93,12 +116,19 @@ module Smash
93
116
  end
94
117
 
95
118
  def to_ruby_file_name(name)
96
- "#{to_snake(name)}.rb"
119
+ name[/\.rb$/].nil? ? "#{to_snake(name)}.rb" : "#{to_snake(name)}"
97
120
  end
98
121
 
99
122
  def to_snake(var)
100
- file_ext = var.to_s[/\.{1}[a-z]+$/] || ''
101
- var.to_s.gsub(/\.\w+$/, '').gsub(/\W/, '_').downcase + file_ext
123
+ var = var.to_s unless var.kind_of? String
124
+
125
+ # var.gsub(/\W/, '_').downcase
126
+ var.gsub(/:{2}|\//, '_').
127
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
128
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
129
+ gsub(/\s+/, '_').
130
+ tr("-", "_").
131
+ downcase
102
132
  end
103
133
 
104
134
  def update_message_body(opts = {})
@@ -13,6 +13,9 @@ module Smash
13
13
  extend Smash::CloudPowers::Synapse::Queue
14
14
  include Smash::CloudPowers::AwsResources
15
15
 
16
+ # gets the instance time or the time it was called and as seconds from
17
+ # epoch
18
+ # TODO: use time codes
16
19
  def boot_time
17
20
  begin
18
21
  @boot_time ||=
@@ -24,6 +27,7 @@ module Smash
24
27
  end
25
28
  end
26
29
 
30
+ # Send a status message on the status Pipe then terminates the instance.
27
31
  def die!
28
32
  Thread.kill(@status_thread) unless @status_thread.nil?
29
33
  # blame = errors.sort_by(&:reverse).last.first
@@ -74,6 +78,7 @@ module Smash
74
78
  end.first
75
79
  end
76
80
 
81
+ # Gets and sets the public hostname of the instance
77
82
  def instance_url
78
83
  @instance_url ||= if env('TESTING')
79
84
  'https://test-url.com'
@@ -83,6 +88,10 @@ module Smash
83
88
  end
84
89
  end
85
90
 
91
+ # Makes the http request to self/meta-data to get all the metadata keys or,
92
+ # if a key is given, the method makes the http request to get that
93
+ # particular key from the metadata
94
+ # @param: [key <String>]
86
95
  def metadata_request(key = '')
87
96
  unless env('TESTING')
88
97
  metadata_uri = "http://169.254.169.254/latest/meta-data/#{key}"
@@ -1,3 +1,3 @@
1
1
  module CloudPowers
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.5"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloud_powers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Phillipps
@@ -159,7 +159,6 @@ executables: []
159
159
  extensions: []
160
160
  extra_rdoc_files: []
161
161
  files:
162
- - ".byebug_history"
163
162
  - ".gitignore"
164
163
  - ".rspec"
165
164
  - ".ruby-version"
@@ -185,7 +184,7 @@ files:
185
184
  - lib/cloud_powers/synapse/synapse.rb
186
185
  - lib/cloud_powers/version.rb
187
186
  - lib/cloud_powers/workflow.rb
188
- homepage: https://smashanalytics.atlassian.net/wiki/display/SB/Cloud+Powers
187
+ homepage: https://github.com/adam-phillipps/cloud_powers
189
188
  licenses:
190
189
  - MIT
191
190
  metadata: {}
@@ -195,9 +194,9 @@ require_paths:
195
194
  - lib
196
195
  required_ruby_version: !ruby/object:Gem::Requirement
197
196
  requirements:
198
- - - ">="
197
+ - - "~>"
199
198
  - !ruby/object:Gem::Version
200
- version: '0'
199
+ version: 2.3.0
201
200
  required_rubygems_version: !ruby/object:Gem::Requirement
202
201
  requirements:
203
202
  - - ">="
data/.byebug_history DELETED
@@ -1,256 +0,0 @@
1
- exit
2
- Smash::CloudPowers.included_modules
3
- Smash::CloudPowers
4
- Pipe
5
- Queue
6
- CloudPowers.public_methods(false)
7
- CloudPowers.public_methods
8
- CloudPowers.included_modules
9
- CloudPowers.methods.sort
10
- CloudPowers
11
- self.methods.sort
12
- self.modules
13
- c
14
- exit
15
- Smash.const_get(to_pascal(task)).new
16
- Smash.const_get(to_pascal(task))
17
- to_pascal(task)
18
- c
19
- exit
20
- n
21
- var.to_s[/\.{1}[a-z]+$/]
22
- l
23
- l-
24
- u = basename(Pathname(var))
25
- u
26
- v
27
- var.to_s.gsub(/\.\w+$/, '').gsub(/\W/, '_').downcase + (file_ext || '')
28
- var.to_s.gsub(/\.\w+$/, '').gsub(/\W/, '_').downcase + file_ext
29
- file_ext = var.to_s[/\.{1}[a-z]+$/]
30
- var.to_s[/\.{1}[a-z]+$/]
31
- var.to_s
32
- eval 'var'
33
- var
34
- s
35
- name
36
- to_snake(name)
37
- s
38
- task
39
- n
40
- body
41
- n
42
- s
43
- c
44
- n
45
- s
46
- c
47
- n
48
- exit
49
- FileUtils::touch(task_path('testinz.rb'))
50
- FileUtils::mkdir_p task_path
51
- task_path
52
- FileUtils::touch(task_path('testinz.rb'))
53
- task_path('testinz.rb')
54
- exit
55
- Pathname(__FILE__).parent.dirname + "tasks/#{to_snake(file)}"
56
- file.empty?
57
- s
58
- exit
59
- Pathname(__FILE__).dirname.parent + 'tasks' + ''
60
- to_snake(t_p)
61
- t_p
62
- file.empty? ? t_p : Pathname(to_snake(t_p))
63
- file
64
- t_p
65
- n
66
- s
67
- c
68
- b = ok['.rb']
69
- ok
70
- ok['.rb']
71
- ok = 'bla.rb'
72
- s3
73
- s3.stub_responses(:list_buckets, [])
74
- c
75
- exit
76
- @uuu
77
- n
78
- exit
79
- File.open '/Users/adam/code/cloud_powers/lib/tasks/testinz.rb'
80
- task_path 'testinz'
81
- task_path
82
- task_path_name
83
- exit
84
- ext
85
- exit
86
- FileUtils.ls(task_path '')
87
- FileUtils.ls(task_path)
88
- task_path('testinz') + '.rb'
89
- c
90
- exit
91
- puts ENV.keys.sort
92
- ENV
93
- key
94
- s
95
- region
96
- s
97
- s3.list_objects(bucket: bucket).contents
98
- bucket = 'jobRequests'
99
- bucket
100
- n
101
- task_path(file).exist?
102
- n
103
- s
104
- n
105
- s
106
- c
107
- s
108
- n
109
- task
110
- n
111
- msg.body
112
- msg
113
- s
114
- Smash::Testinz.new
115
- Smash::Testinz
116
- c
117
- name
118
- s
119
- c
120
- b = OpenStruct.new(name: 'yo', body: {})
121
- require 'ostruct'
122
- k = Struct.new(name: 'ok')
123
- JSON.parse(msg.body)
124
- s
125
- exit
126
- exception
127
- n
128
- { body: {} }.to_json
129
- c
130
- exit
131
- self.class
132
- build
133
- Delegator.build
134
- c
135
- exit
136
- Delegator.build
137
- exit
138
- build
139
- l-
140
- Smash::CloudPowers::Delegator.methods.sort
141
- puts Delegator.class
142
- puts Delegator.methods.sort
143
- Delegator.class
144
- Delegator
145
- c
146
- exit
147
- @instance_url =~ URI::regexp
148
- @instance_url
149
- exit
150
- get_instance_url
151
- instance_url
152
- @instance_url
153
- c
154
- n
155
- c
156
- expect(now).to be_less_than(boot_time)
157
- boot_time
158
- task_name
159
- @boot_time
160
- @task_name
161
- n
162
- s
163
- exit
164
- l
165
- l-
166
- ec2.describe_instances(instance_ids: [ids].flatten).reservations[0].instances[0].tags.select { |t| t.value if t.key == 'taskType' }
167
- env '*'
168
- env *
169
- env
170
- ec2.describe_instances(instance_ids: [ids].flatten).reservations[0].instances[0].tags.select { |t| t.value if t.key == 'taskType' }
171
- ec2.stub_responses
172
- ec2.stub_response
173
- ec2.describe_instances(instance_ids: [ids].flatten)
174
- ids
175
- s
176
- @task_name
177
- s
178
- n
179
- @boot_time
180
- n
181
- c
182
- exit
183
- e
184
- format_error_message e
185
- e
186
- s
187
- format_error_message e
188
- format_error_message
189
- n
190
- ec2.describe_instances(dry_run: env('testing'), instance_ids:[@instance_id]).reservations[0].instances[0].launch_time.to_i
191
- @instance_id
192
- n
193
- s
194
- n
195
- exit
196
- n
197
- value
198
- key
199
- n
200
- key
201
- n
202
- s
203
- block_given?
204
- key
205
- n
206
- s
207
- keys
208
- n
209
- key
210
- n
211
- env('TESTING')
212
- s
213
- exit
214
- get_awareness!
215
- methods.sort
216
- get!
217
- @instance_id
218
- @boot_time
219
- n
220
- s
221
- exit
222
- region
223
- boot_time
224
- exit
225
- f = File.new('.test.env')
226
- `pwd`
227
- pwd
228
- Dotenv.load('.test.env')
229
- Dotenv.load(.test.env)
230
- require 'dotenv'
231
- c
232
- exit
233
- ENV[to_snake(key).upcase]
234
- require 'dotenv'
235
- s
236
- n
237
- s
238
- l-
239
- l
240
- puts ENV.keys.sort
241
- ENV['TESTING']
242
- ENV['testing']
243
- region
244
- n
245
- s
246
- c
247
- exit
248
- q = Boogs.new
249
- exit
250
- build
251
- self.extend Smash::Delegator
252
- exit
253
- build
254
- self.extend Smash::Delegator
255
- exit
256
- self