cloud_powers 0.2.4 → 0.2.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: 536b4abf5027aa70d284971a54fa2ec662cc8983
4
- data.tar.gz: 8bd347c9a99d78f4dce7dec27b837dfa6bd21e22
3
+ metadata.gz: 56d3873e3042a66d3fd0b284b3b5fe88f49803fb
4
+ data.tar.gz: dd5d52611cc92cd66c52a08ecb905e0bc0d28788
5
5
  SHA512:
6
- metadata.gz: 78dd9784282f4d316dce04003efb37cf579b3a52f9c2bd5187f92c6c3afde853cfdeb2038e079a489bf1fcd9fa360121c920ba74829575e0a31fddd02ff8dc9d
7
- data.tar.gz: b59c25e12a62757894c46cc1fdf00be0a3b4ff79b06001f18cd9118af2eaeea4c961e2a646b57841d9007c45918ee91bfeecaaeb6d0b0f40dd3d57a9c00953f2
6
+ metadata.gz: f72cd7f864047a6b1c5060b31fb2d2f75a17df0b22ca72bdc6c545c39091a0e622d94d5db42e2f65840e29c0efd86b5bf2ac98501c7bafa5db4c4466db00f87a
7
+ data.tar.gz: b0403c5ae413ecbf7b94e0c4296384427b38e96381bf2d6c471cf1e8ae8190225e0e6439de7a66ab5b7a20ce83094e369cf5dc593af2355e875f02c2a0e141f9
data/.gitignore CHANGED
@@ -11,3 +11,4 @@
11
11
  .byebug_history
12
12
  lib/cloud_powers/synapse/.DS_Store
13
13
  .DS_Store
14
+ ./**/.DS_Store
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cloud_powers (0.2.4)
4
+ cloud_powers (0.2.5)
5
5
  activesupport-core-ext (~> 4)
6
6
  aws-sdk (~> 2)
7
7
  dotenv (~> 2.1)
data/README.md CHANGED
@@ -2,7 +2,13 @@
2
2
 
3
3
  ## Description
4
4
 
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.
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:
6
+ * SelfAwareness: Gathers data about "self"
7
+ * Synapse: Handles communication needs
8
+ * Delegator: dynamically include Ruby executables from S3
9
+ and more...
10
+
11
+ * 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
12
 
7
13
  Below is a breakdown of the installation, common services provided and an example or 2 of popular methods.
8
14
  _Better docs are on the way_
@@ -11,7 +17,7 @@ _Better docs are on the way_
11
17
 
12
18
  Add this line to your application's Gemfile:
13
19
 
14
- ```ruby
20
+ ```Ruby
15
21
  gem 'cloud_powers'
16
22
  ```
17
23
 
@@ -26,17 +32,21 @@ Or install it yourself as:
26
32
  then either:
27
33
  * set environment variables that matches the below group
28
34
  * fill out a .env file and load it from the class that is using CloudPowers, like this
29
- ```ruby
35
+ Notes:
36
+ * The code does its best in many cases to make a good guess at some of the configuration
37
+ for you but if you set them in your system environment variables or in the .env file,
38
+ it'll have a much better
39
+ ```Ruby
30
40
  require 'dotenv'
31
41
  Dotenv.load('path to your .env file')
32
42
  ```
33
43
  _things you need for pre-v1_:
34
- ```
44
+ ```Ruby
35
45
  # Aws access:
36
46
  AWS_ACCESS_KEY_ID=""
37
47
  AWS_SECRET_ACCESS_KEY=""
38
48
 
39
- # Aws areas and auth-related locations:
49
+ # Aws areas and auth-related locations:
40
50
  AWS_REGION=""
41
51
 
42
52
  # Aws Build info:
@@ -72,11 +82,11 @@ FINISHED_QUEUE_ADDRESS=""
72
82
 
73
83
  ### SelfAwareness
74
84
  * gets and sets info about the instance -> Neuron/Cerebrum/etc)
75
- ```ruby
85
+ ```Ruby
76
86
  get_awareness!
77
87
  ```
78
88
  * 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.
89
+ * See EC2 metadata for details on the instance/node metadata that is set.
80
90
  * Additionally, the instance public hostname, id and a few others are set using other-than-EC2-metadata methods.
81
91
 
82
92
 
@@ -97,7 +107,7 @@ FINISHED_QUEUE_ADDRESS=""
97
107
  * like a task list or a message board for asynchronous communication
98
108
  * Board <Struct>:
99
109
  * interface with Queue config, data, name, etc.
100
- ```ruby
110
+ ```Ruby
101
111
  default_workflow = Workflow.new
102
112
  board = Board.new(default_workflow)
103
113
  board.name
@@ -110,15 +120,15 @@ FINISHED_QUEUE_ADDRESS=""
110
120
  Example usage:
111
121
  1. Give the entire stream name or a symbol or string that is found in the .env for the name of the Queue
112
122
  2. provide a block that will create the record that gets sent to the board
113
- ```ruby
123
+ ```Ruby
114
124
  poll(board_name <string|symbol>, opts <optional config Hash>) { block }
115
125
  ```
116
126
  or
117
- ```ruby
127
+ ```Ruby
118
128
  poll(:backlog) { |msg, stats| Task.new(instance_id, msg) if stats.success? }
119
129
  ```
120
130
  or
121
- ```ruby
131
+ ```Ruby
122
132
  poll(:backlog, wait_time: 30, delete: false) do
123
133
  edited_message = sitrep.merge(foo: 'bar')
124
134
  update = some_method(edited_message)
@@ -126,26 +136,26 @@ FINISHED_QUEUE_ADDRESS=""
126
136
  ```
127
137
  #### Pipe
128
138
  * 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.
139
+ * The Pipe module is for communicating via streams. Piping is meant to be a way to communicate status, problems and other general info or huge result sets and things of that nature. There can be very high traffic through the pipe or none at all. Very soon (probably V-0.2.6 or 7), 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 or the "batch" or "group" or any other concept like that. 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 as a batch ID.
130
140
 
131
141
  Example usage:
132
- ruby```
142
+ ```Ruby
133
143
  pipe_to(stream_name <string/symbol>) { &block }
134
144
  ```
135
- ruby```
145
+ ```Ruby
136
146
  pipe_to(:status_queue) { sitrep(content: 'workflowComplete') }
137
147
  ```
138
148
  and for multiple records (KCL)
139
- ```ruby
149
+ ```Ruby
140
150
  flow_to(stream_name <string/symbol>) { &block }
141
151
  ```
142
- ```ruby
152
+ ```Ruby
143
153
  flow_to(:status_queue) do
144
154
  interesting_instances = neurons.map do |neuron|
145
155
  return neuron if neuron.workflow.done?
146
156
  end
147
157
  find_efficient_neurons(interesting_instances) # this gets sent through the Pipe
148
- end
158
+ end
149
159
  ```
150
160
 
151
161
 
@@ -153,10 +163,6 @@ FINISHED_QUEUE_ADDRESS=""
153
163
  * Allows the nodes to have a collective awareness of each other, the environment they work in and other Jobs (Coming soon...)
154
164
 
155
165
 
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
-
160
166
  ## Development
161
167
 
162
168
  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.
data/cloud_powers.gemspec CHANGED
@@ -4,30 +4,31 @@ $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'
8
- spec.name = 'cloud_powers'
9
- spec.version = CloudPowers::VERSION
10
- spec.author = 'Adam Phillipps'
11
- spec.email = 'adam.phillipps@gmail.com'
12
- spec.summary = %q{Cloud providers wrapper. Currently only AWS is supported.}
13
- spec.description = <<-EOF
7
+ spec.required_ruby_version = '~> 2.3.0'
8
+ spec.name = 'cloud_powers'
9
+ spec.version = CloudPowers::VERSION
10
+ spec.author = 'Adam Phillipps'
11
+ spec.email = 'adam.phillipps@gmail.com'
12
+ spec.summary = %q{Cloud providers wrapper. Currently only AWS is supported.}
13
+ spec.description = <<-EOF
14
14
  CloudPowers is a wrapper around AWS and in the future, other cloud service Providers.
15
15
  It was developed specifically for the Brain project but hopefully can be used
16
16
  in any other ruby project that needs to use cloud service providers' resources.
17
- Version 0.2.3 has a little EC2, S3, SQS, SNS and Kinesis. V 0.2.4 includes
18
- a Context a Workflow revamp. Websockets will be next.
17
+ Version 0.2.5 has a little EC2, S3, SQS, SNS and Kinesis and some a few other
18
+ features you can find in the docs.
19
+ The next versions will have websockets, IoT, more Kinesis and Workflow integration.
19
20
  This project is actively being developed, so more additions, specs and docs
20
- will be updated frequently with new funcionality but the gem will follow good
21
- practices for versioning. I always welcome input.
21
+ will be added and updated frequently with new funcionality but the gem will
22
+ follow good practices for versioning. I always welcome input.
22
23
  Enjoy!
23
24
  EOF
24
- spec.homepage = 'https://github.com/adam-phillipps/cloud_powers'
25
- spec.license = 'MIT'
25
+ spec.homepage = 'https://github.com/adam-phillipps/cloud_powers'
26
+ spec.license = 'MIT'
26
27
 
27
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
- spec.bindir = 'exe'
29
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
- spec.require_paths = ['lib']
28
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
29
+ spec.bindir = 'exe'
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ['lib']
31
32
 
32
33
  spec.add_runtime_dependency 'activesupport-core-ext', '~> 4'
33
34
  spec.add_runtime_dependency 'aws-sdk', '~> 2'
@@ -13,43 +13,63 @@ module Smash
13
13
  zfind(:aws_region) || 'us-west-2'
14
14
  end
15
15
 
16
- def ec2
17
- @ec2 ||= Aws::EC2::Client.new(
16
+ def ec2(opts = {})
17
+ config = {
18
+ stub_responses: false,
18
19
  region: region,
19
20
  credentials: Auth.creds
20
- )
21
+ }
22
+ config = config.merge(opts.select { |k| config.key?(k) })
23
+
24
+ @ec2 ||= Aws::EC2::Client.new(config)
21
25
  end
22
26
 
23
- def image(name)
24
- ec2.describe_images(
27
+ def image(name, opts = {})
28
+ config = {
25
29
  filters: [{ name: 'tag:aminame', values: [name.to_s] }]
26
- ).images.first
30
+ }
31
+ config = config.merge(opts.select { |k| config.key?(k) })
32
+
33
+ ec2(opts).describe_images(config).images.first
27
34
  end
28
35
 
29
- def kinesis
30
- @kinesis ||= Aws::Kinesis::Client.new(
36
+ def kinesis(opts = {})
37
+ config = {
38
+ stub_responses: false,
31
39
  region: region,
32
- credentials: Auth.creds,
33
- )
40
+ credentials: Auth.creds
41
+ }
42
+ config = config.merge(opts.select { |k| config.key?(k) })
43
+
44
+ @kinesis ||= Aws::Kinesis::Client.new(config)
34
45
  end
35
46
 
36
- def s3
37
- @s3 ||= Aws::S3::Client.new(
47
+ def s3(opts = {})
48
+ config = {
49
+ stub_responses: false,
38
50
  region: region,
39
51
  credentials: Auth.creds
40
- )
52
+ }
53
+ config = config.merge(opts.select { |k| config.key?(k) })
54
+
55
+ @s3 ||= Aws::S3::Client.new(config)
41
56
  end
42
57
 
43
- def sns
44
- @sns ||= Aws::SNS::Client.new(
58
+ def sns(opts = {})
59
+ config = {
60
+ stub_responses: false,
45
61
  region: region,
46
62
  credentials: Auth.creds
47
- )
63
+ }
64
+ config = config.merge(opts.select { |k| config.key?(k) })
65
+
66
+ @sns ||= Aws::SNS::Client.new(config)
48
67
  end
49
68
 
50
- def sqs
51
- @sqs ||= Aws::SQS::Client.new(
52
- credentials: Auth.creds
69
+ def sqs(opts = {})
70
+ @sqs ||= Aws::SQS::Client.new({
71
+ credentials: Auth.creds
72
+ }.merge(opts)
53
73
  )
54
74
  end
55
75
  end
@@ -28,7 +28,6 @@ module Smash
28
28
  # === @returns
29
29
  # `Smash::Context`
30
30
  def initialize(args)
31
- byebug unless valid_args?(args)
32
31
  unless valid_args?(args)
33
32
  raise ArgumentError.new 'Can be either a Hash, JSON, or an Enumerable ' +
34
33
  "arguments: #{args}"
@@ -63,9 +62,10 @@ module Smash
63
62
  @structure.to_a
64
63
  end
65
64
 
66
- #
65
+ # A Hash that represents the resources and some configuration for them
66
+ # === @returns Hash
67
67
  def structure
68
- symbolize_keys(@structure)
68
+ modify_keys_with(@structure) { |key| key.to_sym }
69
69
  end
70
70
 
71
71
  # Valid scheme for @structure is assured by running the arguments through
@@ -8,36 +8,112 @@ module Smash
8
8
  module CloudPowers
9
9
  module Helper
10
10
 
11
+ # Sets an Array of instance variables, individually to a value that a
12
+ # user given block returns.
13
+ # === @params Array
14
+ # * each object will be used as the name for the instance variable that
15
+ # your block returns
16
+ # === @&block (optional)
17
+ # * this is called for each object in the Array and is used as the value
18
+ # for the instance variable that is being named and created for each key
19
+ # === @return Array
20
+ # * each object will either be the result of `#instance_variable_set(key, value)`
21
+ # or instance_variable_get(key)
22
+ # === Sampel use:
23
+ # keys = ['foo', 'bar', 'yo']
24
+ #
25
+ # attr_map!(keys) { |key| sleep 1; "#{key}:#{Time.now.to_i}" }
26
+ # # => ['foo:1475434058', 'bar:1475434059', 'yo:1475434060']
27
+ #
28
+ # puts @bar
29
+ # # => 'bar:1475434059'
11
30
  def attr_map!(keys)
12
- keys.map do |attr|
13
- key = to_i_var(attr)
31
+ keys.map do |key|
32
+ new_i_var = to_i_var(key)
14
33
  value = yield key if block_given?
15
- instance_variable_set(key, value) unless instance_variable_get(to_i_var(key))
34
+ instance_variable_set(new_i_var, value) unless instance_variable_get(new_i_var)
16
35
  end
17
36
  end
18
37
 
19
38
  # This is a way to find out if you are trying to work with a resource
20
39
  # available to CloudPowers
21
40
  # === @returns <Array>
41
+ # * Use `.constants` to find all the modules and classes available.
42
+ # Notes:
43
+ # TODO: make this smartly pick up all the objects, within reason and
44
+ # considering need, that we have access to
22
45
  def available_resources
23
46
  [:Task].concat(Smash::CloudPowers.constants)
24
47
  end
25
48
 
49
+ # Does its best job at guessing where this method was called from, in terms
50
+ # of where it is located on the file system. It helps track down where a
51
+ # project root is etc.
26
52
  def called_from
27
53
  File.expand_path(File.dirname($0))
28
54
  end
29
55
 
56
+ # creates a default logger
57
+ # Notes:
58
+ # * TODO: at least make this have overridable defaults
30
59
  def create_logger
31
60
  logger = Logger.new(STDOUT)
32
61
  logger.datetime_format = '%Y-%m-%d %H:%M:%S'
33
62
  logger
34
63
  end
35
64
 
65
+ # Allows you to modify all keys, including nested, with a block that you pass.
66
+ # If no block is passed, a copy is returned.
67
+ # === @params
68
+ # * params Hash|Array
69
+ # === @&block (optional)
70
+ # * should modify the key and return that value so it can be used in the copy
71
+ # === @returns
72
+ # * a copy of the given Array or Hash, with all Hash keys modified
73
+ # === Sample use:
74
+ # hash = { 'foo' => 'v1', 'bar' => { fleep: { 'florp' => 'yo' } } }
75
+ # modify_keys_with(hash) { |key| key.to_sym }
76
+ # # => { foo: 'v1', bar: { fleep: { florp: 'yo' } } }
77
+ # === Notes:
78
+ # * see `#modify_keys_with()` for handling first-level keys
79
+ # * see `#pass_the_buck()` for the way nested structures are handled
80
+ # * case for different types taken from _MultiXML_ (multi_xml.rb)
81
+ # * TODO: look at optimization
82
+ def deep_modify_keys_with(params)
83
+ case params
84
+ when Hash
85
+ params.inject({}) do |carry, (k, v)|
86
+ carry.tap do |h|
87
+ if block_given?
88
+ key = yield k
89
+
90
+ value = if v.kind_of?(Hash)
91
+ deep_modify_keys_with(v) do |new_key|
92
+ Proc.new.call(new_key)
93
+ end
94
+ else
95
+ v
96
+ end
97
+
98
+ h[key] = value
99
+ else
100
+ h[k] = v
101
+ end
102
+ end
103
+ end
104
+ when Array
105
+ params.map{ |value| symbolize_keys(value) }
106
+ else
107
+ params
108
+ end
109
+ end
110
+
36
111
  def errors
37
- # TODO: needs work
112
+ # TODO: wow...needs work
38
113
  $errors ||= SmashError.instance
39
114
  end
40
115
 
116
+ # Join the message and backtrace into a String with line breaks
41
117
  def format_error_message(error)
42
118
  begin
43
119
  [error.message, error.backtrace.join("\n")].join("\n")
@@ -58,11 +134,35 @@ module Smash
58
134
  @logger ||= create_logger
59
135
  end
60
136
 
137
+ # Allows you to modify all first-level keys with a block that you pass.
138
+ # If no block is passed, a copy is returned.
139
+ # === @params
140
+ # * params Hash|Array
141
+ # === @&block (optional)
142
+ # * should modify the key and return that value so it can be used in the copy
143
+ # === @returns
144
+ # * a copy of the given Array or Hash, with all Hash keys modified
145
+ # === Sample use:
146
+ # hash = { 'foo' => 'v1', 'bar' => { fleep: { 'florp' => 'yo' } } }
147
+ # modify_keys_with(hash) { |k| k.to_sym }
148
+ # # => { :foo => 'v1', :bar => { fleep: { 'florp' => 'yo' } } }
149
+ # === Notes:
150
+ # * see `#deep_modify_keys_with()` for handling nested keys
151
+ # * case for different types taken from _MultiXML_ (multi_xml.rb)
152
+ def modify_keys_with(params)
153
+ params.inject({}) do |carry, (k, v)|
154
+ carry.tap do |h|
155
+ key = block_given? ? (yield k) : k
156
+ h[key] = v
157
+ end
158
+ end
159
+ end
160
+
61
161
  # Lets you retry a piece of logic with 1 second sleep in between attempts
62
162
  # until another bit of logic does what it's supposed to, kind of like
63
163
  # continuing to poll something and doing something when a package is ready
64
164
  # to be taken and processed.
65
- # @params:
165
+ # === @params:
66
166
  # * [allowed_attempts] or Infinity(default) <Number>: The number of times
67
167
  # the loop should be allowed to...well, loop, before a failed retry occurs.
68
168
  # * &test <Block>: A predicate method or block of code that is callable
@@ -81,14 +181,10 @@ module Smash
81
181
  end
82
182
  end
83
183
 
84
- def symbolize_keys(hash)
85
- hash.inject({}) { |carry, (k, v)| carry.tap { |h| h[k.to_sym] = v } }
86
- end
87
-
88
184
  # Gives the path from the project root to lib/tasks[/#{file}.rb]
89
- # @params:
185
+ # === @params:
90
186
  # * [file] <String>: name of a file
91
- # @returns:
187
+ # === @returns:
92
188
  # * path[/file] <String>
93
189
  # * If a `file` is given, it will have a '.rb' file extension
94
190
  # * If no `file` is given, it will return the `#task_require_path`
@@ -98,16 +194,20 @@ module Smash
98
194
  end
99
195
 
100
196
  # Gives the path from the project root to lib/tasks[/file]
101
- # @params:
102
- # * [file] <String>: name of a file
103
- # @returns:
104
- # * path[/file] <String>
197
+ # === @params String (optional)
198
+ # * file_name name of a file
199
+ # === @returns:
200
+ # * path[/file] String
105
201
  # * Neither path nor file will have a file extension
106
202
  def task_require_path(file_name = '')
107
203
  file = File.basename(file_name, File.extname(file_name))
108
204
  Pathname(__FILE__).parent.dirname + 'tasks' + file
109
205
  end
110
206
 
207
+ # Change strings into camelCase
208
+ # === @params var String
209
+ # === @returns String
210
+ # * givenString
111
211
  def to_camel(var)
112
212
  var = var.to_s unless var.kind_of? String
113
213
  step_one = to_snake(var)
@@ -115,20 +215,56 @@ module Smash
115
215
  step_two[0, 1].downcase + step_two[1..-1]
116
216
  end
117
217
 
218
+ # Change strings hyphen-delimited-string
219
+ # === @params var String
220
+ # === @returns String
221
+ # * given-string
222
+ def to_hyph(var)
223
+ var = var.to_s unless var.kind_of? String
224
+
225
+ var.gsub(/:{2}|\//, '-').
226
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
227
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
228
+ gsub(/\s+/, '-').
229
+ tr("_", "-").
230
+ gsub(/^\W/, '').
231
+ downcase
232
+ end
233
+
234
+ # Change strings into snake_case and add '@' at the front
235
+ # === @params var String
236
+ # === @returns String
237
+ # * @given_string
118
238
  def to_i_var(var)
119
239
  var = var.to_s unless var.kind_of? String
120
240
  /^\W*@\w+/ =~ var ? to_snake(var) : "@#{to_snake(var)}"
121
241
  end
122
242
 
243
+ # Change strings into PascalCase
244
+ # === @params var String
245
+ # === @returns String
246
+ # * givenString
123
247
  def to_pascal(var)
124
248
  var = var.to_s unless var.kind_of? String
125
249
  var.gsub(/^(.{1})|\W.{1}|\_.{1}/) { |s| s.gsub(/[^a-z0-9]+/i, '').capitalize }
126
250
  end
127
251
 
252
+ # Change strings into a ruby_file_name with extension
253
+ # === @params var String
254
+ # === @returns String
255
+ # * given_string.rb
256
+ # * includes ruby file extension
257
+ # * see #to_snake()
128
258
  def to_ruby_file_name(name)
129
259
  name[/\.rb$/].nil? ? "#{to_snake(name)}.rb" : "#{to_snake(name)}"
130
260
  end
131
261
 
262
+ # Change strings into snake_case
263
+ # === @params var String
264
+ # === @returns String
265
+ # * given_string
266
+ # * will not have file extensions
267
+ # * see #to_ruby_file_name()
132
268
  def to_snake(var)
133
269
  var = var.to_s unless var.kind_of? String
134
270
 
@@ -140,13 +276,17 @@ module Smash
140
276
  downcase
141
277
  end
142
278
 
279
+ # This method provides a default overrideable message body for things like
280
+ # basic status updates.
281
+ # === @params Hash
282
+ # - instanceId:
283
+ # === Notes:
284
+ # - camel casing is used on the keys because most other languages prefer
285
+ # that and it's not a huge problem in ruby. Besides, there's some other
286
+ # handy methods in this module to get you through those issues, like
287
+ # `#to_snake()` and or `#symbolize_keys_with()`
143
288
  def update_message_body(opts = {})
144
- # TODO: find better implementation of merging nested hashes
145
- # this should be fixed with Job #sitrep_message
146
- # TODO: find a way to trim this method down and get rid
147
- # of a lof of the repitition with these messages
148
- # IDEA: throw events and have a separate thread listening. the separate
149
- # thread could be a communication or status update thread
289
+ # TODO: Better implementation of merging message bodies and config needed
150
290
  unless opts.kind_of? Hash
151
291
  update = opts.to_s
152
292
  opts[:extraInfo] = { message: update }
@@ -0,0 +1,60 @@
1
+ require_relative 'auth'
2
+ require_relative 'helper'
3
+ require_relative 'zenv'
4
+
5
+ module Smash
6
+ module CloudPowers
7
+ module Node
8
+ include Smash::CloudPowers::Auth
9
+ include Smash::CloudPowers::Helper
10
+ include Smash::CloudPowers::Zenv
11
+ # These are sensible defaults that can be overriden by providing a Hash as a param.
12
+ # @params [opts <Hash>]
13
+ # the opts Hash should have values that should be used instead of the given
14
+ # configuration.
15
+ def node_config(opts = {})
16
+ {
17
+ dry_run: zfind(:testing) || false,
18
+ image_id: image('crawlbotprod').image_id, # image(:neuron).image_id
19
+ instance_type: 't2.nano',
20
+ min_count: opts[:max_count] || 0,
21
+ max_count: 0,
22
+ key_name: 'crawlBot',
23
+ security_groups: ['webCrawler'],
24
+ security_group_ids: ['sg-940edcf2'],
25
+ placement: { availability_zone: 'us-west-2c' },
26
+ disable_api_termination: 'false',
27
+ instance_initiated_shutdown_behavior: 'terminate'
28
+ }.merge(opts)
29
+ end
30
+
31
+ # Uses `Aws::EC2#run_instances` to create nodes (Neurons or Cerebrums), at
32
+ # a rate of 0..(n <= 100) at a time, until the required number of instances
33
+ # has been started. The #instance_config() method is used to create instance
34
+ # configuration for the #run_instances method by using the opts hash that was
35
+ # provided as a parameter.
36
+ #
37
+ # === @params opts Hash (optional)
38
+ # an optional instance configuration hash can be passed, which will override
39
+ # the values in the default configuration returned by #instance_config()
40
+ def spin_up_neurons(opts = {})
41
+ ids = nil
42
+ begin
43
+ response = ec2.run_instances(node_config(opts))
44
+ ids = response.instances.map(&:instance_id)
45
+
46
+ ec2.wait_until(:instance_running, instance_ids: ids) do
47
+ logger.info "waiting for #{ids.count} Neurons to start..."
48
+ end
49
+
50
+ # tag(ids, { key: 'task', value: to_camel(self.class.to_s) })
51
+ rescue Aws::EC2::Errors::DryRunOperation => e
52
+ ids = (1..(opts[:max_count] || 0)).to_a.map { |n| n.to_s }
53
+ logger.info "waiting for #{ids.count} Neurons to start..."
54
+ end
55
+
56
+ ids
57
+ end
58
+ end
59
+ end
60
+ end
@@ -17,14 +17,14 @@ module Smash
17
17
 
18
18
  # Gets the instance time or the time it was called and as seconds from
19
19
  # epoch
20
+ # === @returns Integer
20
21
  # TODO: use time codes
21
22
  def boot_time
22
23
  begin
23
24
  @boot_time ||=
24
- ec2.describe_instances(dry_run: zfind('testing'), instance_ids:[@instance_id]).
25
+ ec2.describe_instances(dry_run: zfind(:testing), instance_ids:[instance_id]).
25
26
  reservations[0].instances[0].launch_time.to_i
26
- # rescue Aws::EC2::Errors::DryRunOperation => e
27
- rescue Exception => e
27
+ rescue Aws::EC2::Errors::DryRunOperation => e
28
28
  logger.info "dry run for testing: #{e}"
29
29
  @boot_time ||= Time.now.to_i # comment the code below for development mode
30
30
  end
@@ -67,28 +67,18 @@ module Smash
67
67
  instance_url # gets and sets @instance_url
68
68
  end
69
69
 
70
- # Check self-tags for 'task' and act as an attr_accessor.
71
- # A different node's tag's can be checked for a task by passing
72
- # the id param
73
- # see also: SelfAwareness#task_names
74
- def task_name(id = @instance_id)
75
- # get @task_name
76
- return @task_name unless @task_name.nil?
77
- # set @task_name
78
- # TODO: get all tasks instead of just the first
79
- resp = ec2.describe_instances(instance_ids: [id].flatten).reservations.first
80
- return @task_name = nil if resp.nil?
81
- @task_name = resp.instances[0].tags.select do |t|
82
- t.value if t.key == 'taskType'
83
- end.first
70
+ # Assures there is always a valid instance id because many other Aws calls require it
71
+ # === @returns: String
72
+ def instance_id
73
+ @instance_id ||= metadata_request('instance_id')
84
74
  end
85
75
 
86
76
  # Gets and sets the public hostname of the instance
87
77
  def instance_url
88
78
  @instance_url ||= if zfind('TESTING')
89
- 'https://test-url.com'
79
+ 'abc-1234'
90
80
  else
91
- hostname_uri = 'http://169.254.169.254/latest/meta-data/public-hostname'
81
+ hostname_uri = 'http://169.254.169.254/latest/meta-data/instance-id'
92
82
  HTTParty.get(hostname_uri).parsed_response
93
83
  end
94
84
  end
@@ -96,28 +86,35 @@ module Smash
96
86
  # Makes the http request to self/meta-data to get all the metadata keys or,
97
87
  # if a key is given, the method makes the http request to get that
98
88
  # particular key from the metadata
99
- # @param: [key <String>]
89
+ # === @param: key String (optional)
90
+ # === @returns:
100
91
  def metadata_request(key = '')
101
- unless zfind('TESTING')
102
- metadata_uri = "http://169.254.169.254/latest/meta-data/#{key}"
103
- HTTParty.get(metadata_uri).parsed_response.split("\n")
104
- else
105
-
106
- @z ||= ['i-9254d106', 'ami-id', 'ami-launch-index', 'ami-manifest-path', 'network/thing']
107
- if key == ''
108
- @boogs = ['instance-id', 'ami-id', 'ami-launch-index', 'ami-manifest-path', 'network/interfaces/macs/mac/device-number']
92
+ key = to_hyph(key)
93
+ begin
94
+ unless zfind('TESTING')
95
+ metadata_uri = "http://169.254.169.254/latest/meta-data/#{key}"
96
+ HTTParty.get(metadata_uri).parsed_response.split("\n")
109
97
  else
110
- @z.shift
98
+ require_relative '../../spec/stubs/aws_stubs'
99
+ stubbed_metadata = Smash::CloudPowers::AwsStubs::INSTANCE_METADATA_STUB
100
+
101
+ key.empty? ? stubbed_metadata.keys : stubbed_metadata[to_hyph(key)]
111
102
  end
103
+ rescue Exception => e
104
+ logger.fatal format_error_message e
112
105
  end
113
106
  end
114
107
 
108
+ # Return the time since boot_time
109
+ # === @returns: Integer
110
+ # Notes:
111
+ # * TODO: refactor to use valid time stamps for better tracking.
112
+ # * reason -> separate regions or OSs etc.
115
113
  def run_time
116
- # TODO: refactor to use valid time stamps for better tracking.
117
- # reason -> separate regions or OSs etc.
118
114
  Time.now.to_i - boot_time
119
115
  end
120
116
 
117
+ # Send a message on a Pipe at an interval
121
118
  def send_frequent_status_updates(opts = {})
122
119
  sleep_time = opts.delete(:interval) || 10
123
120
  stream = opts.delete(:stream_name)
@@ -129,6 +126,10 @@ module Smash
129
126
  end
130
127
  end
131
128
 
129
+ # Get the instance status.
130
+ # === @params: id String (optional)
131
+ # * if no id is given, self-instance ID is returned
132
+ # === @returns: String
132
133
  def status(id = @instance_id)
133
134
  begin
134
135
  ec2.describe_instances(dry_run: zfind('TESTING'), instance_ids: [id]).
@@ -139,6 +140,22 @@ module Smash
139
140
  end
140
141
  end
141
142
 
143
+ # Check self-tags for 'task' and act as an attr_accessor.
144
+ # A different node's tag's can be checked for a task by passing
145
+ # the id param
146
+ # see also: SelfAwareness#task_names
147
+ def task_name(id = @instance_id)
148
+ # get @task_name
149
+ return @task_name unless @task_name.nil?
150
+ # set @task_name
151
+ # TODO: get all tasks instead of just the first
152
+ resp = ec2.describe_instances(instance_ids: [id].flatten).reservations.first
153
+ return @task_name = nil if resp.nil?
154
+ @task_name = resp.instances[0].tags.select do |t|
155
+ t.value if t.key == 'taskType'
156
+ end.first
157
+ end
158
+
142
159
  # This method will return true if:
143
160
  # * The run time is more than 5 minutes
144
161
  # and
@@ -66,8 +66,13 @@ module Smash
66
66
  # Creates an actual Queue in SQS using the standard format for a queue name (camel case)
67
67
  # @returns: Queue::Board
68
68
  def create_queue!
69
- sqs.create_queue(queue_name: to_camel(@name))
70
- self
69
+ begin
70
+ sqs.create_queue(queue_name: to_camel(@name))
71
+ self
72
+ rescue Aws::SQS::Errors::QueueDeletedRecently => e
73
+ sleep 5
74
+ retry
75
+ end
71
76
  end
72
77
 
73
78
  # Deletes an actual Queue from SQS
@@ -1,3 +1,3 @@
1
1
  module CloudPowers
2
- VERSION = "0.2.4"
2
+ VERSION = "0.2.5"
3
3
  end
data/lib/cloud_powers.rb CHANGED
@@ -3,6 +3,7 @@ require 'cloud_powers/aws_resources'
3
3
  require 'cloud_powers/context'
4
4
  require 'cloud_powers/delegator'
5
5
  require 'cloud_powers/helper'
6
+ require 'cloud_powers/node'
6
7
  require 'cloud_powers/self_awareness'
7
8
  require 'cloud_powers/storage'
8
9
  require 'cloud_powers/version'
@@ -17,5 +18,6 @@ module Smash
17
18
  include Smash::CloudPowers::SelfAwareness
18
19
  include Smash::CloudPowers::Storage
19
20
  include Smash::CloudPowers::Synapse
21
+ include Smash::CloudPowers::Node
20
22
  end
21
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloud_powers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Phillipps
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-10-02 00:00:00.000000000 Z
11
+ date: 2016-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport-core-ext
@@ -154,11 +154,12 @@ description: |2
154
154
  CloudPowers is a wrapper around AWS and in the future, other cloud service Providers.
155
155
  It was developed specifically for the Brain project but hopefully can be used
156
156
  in any other ruby project that needs to use cloud service providers' resources.
157
- Version 0.2.3 has a little EC2, S3, SQS, SNS and Kinesis. V 0.2.4 includes
158
- a Context a Workflow revamp. Websockets will be next.
157
+ Version 0.2.5 has a little EC2, S3, SQS, SNS and Kinesis and some a few other
158
+ features you can find in the docs.
159
+ The next versions will have websockets, IoT, more Kinesis and Workflow integration.
159
160
  This project is actively being developed, so more additions, specs and docs
160
- will be updated frequently with new funcionality but the gem will follow good
161
- practices for versioning. I always welcome input.
161
+ will be added and updated frequently with new funcionality but the gem will
162
+ follow good practices for versioning. I always welcome input.
162
163
  Enjoy!
163
164
  email: adam.phillipps@gmail.com
164
165
  executables: []
@@ -179,12 +180,12 @@ files:
179
180
  - bin/setup
180
181
  - cloud_powers.gemspec
181
182
  - lib/cloud_powers.rb
182
- - lib/cloud_powers/.DS_Store
183
183
  - lib/cloud_powers/auth.rb
184
184
  - lib/cloud_powers/aws_resources.rb
185
185
  - lib/cloud_powers/context.rb
186
186
  - lib/cloud_powers/delegator.rb
187
187
  - lib/cloud_powers/helper.rb
188
+ - lib/cloud_powers/node.rb
188
189
  - lib/cloud_powers/self_awareness.rb
189
190
  - lib/cloud_powers/smash_error.rb
190
191
  - lib/cloud_powers/storage.rb
Binary file