cloud_powers 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 52e44795ffd76ed0a74e12a312f7602c908f95a4
4
+ data.tar.gz: 31eda48bf52bfe9fc3e36048e979db2bea25d56f
5
+ SHA512:
6
+ metadata.gz: acb3cf5189b2384c691792a2fdf865cae9345a964ca0f51dac06d0a78d9427b73648d0592891a8a743b36df4cd483c7fcda204d9fedb5debb3c1a8697014b1fc
7
+ data.tar.gz: 4f8a188451bc4126af3ed04b61ffba0c6852df7dff41c365033435ce26958a7aaf7e9c9ed4aca803ac23c249242f42bd9f9cc7d20d55fc5d2659b409179c2ec2
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.1
5
+ before_install: gem install bundler -v 1.12.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cloud_powers.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cloud_powers (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.5)
10
+ rake (10.4.2)
11
+ rspec (3.5.0)
12
+ rspec-core (~> 3.5.0)
13
+ rspec-expectations (~> 3.5.0)
14
+ rspec-mocks (~> 3.5.0)
15
+ rspec-core (3.5.3)
16
+ rspec-support (~> 3.5.0)
17
+ rspec-expectations (3.5.0)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.5.0)
20
+ rspec-mocks (3.5.0)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.5.0)
23
+ rspec-support (3.5.0)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ bundler (~> 1.12)
30
+ cloud_powers!
31
+ rake (~> 10.0)
32
+ rspec (~> 3.0)
33
+
34
+ BUNDLED WITH
35
+ 1.12.5
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Adam Phillipps
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # CloudPowers
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.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'cloud_powers'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install cloud_powers
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ 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.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/cloud_powers.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
41
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "cloud_powers"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cloud_powers/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cloud_powers"
8
+ spec.version = CloudPowers::VERSION
9
+ spec.authors = ["Adam Phillipps"]
10
+ spec.email = ["adam.phillipps@gmail.com"]
11
+
12
+ spec.summary = %q{Cloud providers wrapper. Currently only AWS is supported.}
13
+ spec.description = %q{CloudPowers is a wrapper around AWS and other cloud services. This wrapper was developed specifically for the Brain project but can be used in any other ruby project.}
14
+ spec.homepage = "https://github.com/adam-phillipps/cloud_powers"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
21
+ # else
22
+ # raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ # end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.12"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rspec", "~> 3.0"
33
+ end
@@ -0,0 +1,20 @@
1
+ require 'cloud_powers/version'
2
+ require 'cloud_powers/auth'
3
+ require 'cloud_powers/aws_resources'
4
+ require 'cloud_powers/delegator'
5
+ require 'cloud_powers/helper'
6
+ require 'cloud_powers/self_awareness'
7
+ require 'cloud_powers/storage'
8
+ require 'cloud_powers/workflow'
9
+
10
+ module Smash
11
+ module CloudPowers
12
+ extend Smash::CloudPowers::Auth
13
+ extend Smash::CloudPowers::Delegator
14
+ include Smash::CloudPowers::AwsResources
15
+ include Smash::CloudPowers::Helper
16
+ include Smash::CloudPowers::SelfAwareness
17
+ include Smash::CloudPowers::Storage
18
+ include Smash::CloudPowers::Synapse
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ require 'aws-sdk'
2
+ Aws.use_bundled_cert!
3
+
4
+ module Smash
5
+ module CloudPowers
6
+ module Auth
7
+ def self.creds
8
+ @creds ||= Aws::Credentials.new(
9
+ ENV['AWS_ACCESS_KEY_ID'],
10
+ ENV['AWS_SECRET_ACCESS_KEY']
11
+ )
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'auth'
2
+
3
+ module Smash
4
+ module CloudPowers
5
+ module AwsResources
6
+ extend Smash::CloudPowers::Auth
7
+
8
+ def region
9
+ env('Aws Region')
10
+ end
11
+
12
+ def ec2
13
+ @ec2 ||= Aws::EC2::Client.new(
14
+ region: region,
15
+ credentials: Auth.creds
16
+ )
17
+ end
18
+
19
+ def image(name)
20
+ ec2.describe_images(
21
+ filters: [{ name: 'tag:aminame', values: [name.to_s] }]
22
+ ).images.first
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'auth'
2
+ require_relative 'aws_resources'
3
+ require_relative 'helper'
4
+ require_relative 'storage'
5
+ require 'byebug'
6
+
7
+ module Smash
8
+ module Delegator
9
+ extend Smash::CloudPowers::Auth
10
+ include Smash::CloudPowers::AwsResources
11
+ include Smash::CloudPowers::Helper
12
+ include Smash::CloudPowers::Storage
13
+
14
+ def build(id, msg)
15
+ body = JSON.parse(msg.body)
16
+ begin
17
+ task = body.delete('task')
18
+ if approved_task? task
19
+ source_task(task)
20
+ require_relative task_require_path(task)
21
+ Smash.const_get(to_pascal(task)).new(id, msg)
22
+ else
23
+ Task.new(id, msg) # returns a default Task
24
+ end
25
+ rescue JSON::ParserError => e
26
+ message = [msg.body, format_error_message(e)].join("\n")
27
+ logger.info "Message in backlog is ill-formatted: #{message}"
28
+ pipe_to(:status_stream) { sitrep(extraInfo: { message: message }) }
29
+ end
30
+ end
31
+
32
+ def approved_task?(name = nil)
33
+ # TODO: improve this
34
+ ['demo', 'testinz'].include? name
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,121 @@
1
+ require 'logger'
2
+ require 'pathname'
3
+ require 'syslog/logger'
4
+ require_relative 'smash_error'
5
+ require_relative 'auth'
6
+ require 'byebug'
7
+
8
+ module Smash
9
+ module CloudPowers
10
+ module Helper
11
+ include Smash::CloudPowers::Auth
12
+
13
+ def attr_map!(keys)
14
+ keys.map do |attr|
15
+ key = to_i_var(attr)
16
+ value = yield key if block_given?
17
+ instance_variable_set(key, value)
18
+ end
19
+ end
20
+
21
+ def env(key)
22
+ ENV[to_snake(key).upcase]
23
+ end
24
+
25
+ def create_logger
26
+ logger = Logger.new(STDOUT)
27
+ logger.datetime_format = '%Y-%m-%d %H:%M:%S'
28
+ logger
29
+ end
30
+
31
+ def errors
32
+ # TODO: needs work
33
+ $errors ||= SmashError.instance
34
+ end
35
+
36
+ def format_error_message(error)
37
+ begin
38
+ [error.message, error.backtrace.join("\n")].join("\n")
39
+ rescue Exception => e
40
+ # if the formatting won't work, return the original exception
41
+ error
42
+ end
43
+ end
44
+
45
+ def log_file
46
+ @log_file ||= env('LOG_FILE')
47
+ end
48
+
49
+ def logger
50
+ @logger ||= create_logger
51
+ end
52
+
53
+ # Sample usage:
54
+ # 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)
57
+ result = yield if block_given?
58
+ tries = 1
59
+ until test.call(result) || tries >= allowed_attempts
60
+ result = yield if block_given?
61
+ sleep 1
62
+ end
63
+ end
64
+
65
+ def task_path(file)
66
+ Pathname(__FILE__).parent.dirname + 'tasks' + to_ruby_file_name(file)
67
+ end
68
+
69
+ def task_require_path(file_name)
70
+ file = File.basename(file_name, File.extname(file_name))
71
+ Pathname(__FILE__).parent.dirname + 'tasks' + file
72
+ end
73
+
74
+ def to_camel(var)
75
+ var = var.to_s unless var.kind_of? String
76
+ step_one = to_snake(var)
77
+ step_two = to_pascal(step_one)
78
+ step_two[0, 1].downcase + step_two[1..-1]
79
+ end
80
+
81
+ def to_i_var(var)
82
+ "@#{to_snake(var)}"
83
+ end
84
+
85
+ def to_pascal(var)
86
+ var = var.to_s unless var.kind_of? String
87
+ var.gsub(/^(.{1})|\W.{1}|\_.{1}/) { |s| s.gsub(/[^a-z0-9]+/i, '').capitalize }
88
+ end
89
+
90
+ def to_ruby_file_name(name)
91
+ "#{to_snake(name)}.rb"
92
+ end
93
+
94
+ def to_snake(var)
95
+ var = var.to_s unless var.kind_of? String
96
+ var.gsub(/\W/, '_').downcase
97
+ end
98
+
99
+ def update_message_body(opts = {})
100
+ # TODO: find better implementation of merging nested hashes
101
+ # this should be fixed with Job #sitrep_message
102
+ # TODO: find a way to trim this method down and get rid
103
+ # of a lof of the repitition with these messages
104
+ # IDEA: throw events and have a separate thread listening. the separate
105
+ # thread could be a communication or status update thread
106
+ unless opts.kind_of? Hash
107
+ update = opts.to_s
108
+ opts[:extraInfo] = { message: update }
109
+ end
110
+ updated_extra_info = opts.delete(:extraInfo) || {}
111
+
112
+ {
113
+ instanceId: @instance_id || 'none-aquired',
114
+ type: 'status-update',
115
+ content: 'running',
116
+ extraInfo: updated_extra_info
117
+ }.merge(opts)
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,133 @@
1
+ require 'aws-sdk'
2
+ Aws.use_bundled_cert!
3
+ require 'httparty'
4
+ require_relative 'helper'
5
+ require_relative './synapse/synapse'
6
+
7
+ module Smash
8
+ module CloudPowers
9
+ extend Smash::CloudPowers::Synapse::Pipe
10
+ extend Smash::CloudPowers::Synapse::Queue
11
+
12
+ module SelfAwareness
13
+ extend Smash::CloudPowers::Helper
14
+ extend Smash::CloudPowers::Synapse::Pipe
15
+ extend Smash::CloudPowers::Synapse::Queue
16
+
17
+ def boot_time
18
+ begin
19
+ @boot_time ||=
20
+ ec2.describe_instances(dry_run: env('testing'), instance_ids:[@instance_id]).
21
+ reservations[0].instances[0].launch_time.to_i
22
+ rescue Aws::EC2::Errors::DryRunOperation => e
23
+ logger.info "dry run for testing: #{format_error_message(e)}"
24
+ @boot_time ||= Time.now.to_i # comment the code below for development mode
25
+ end
26
+ end
27
+
28
+ def die!
29
+ Thread.kill(@status_thread) unless @status_thread.nil?
30
+ # blame = errors.sort_by(&:reverse).last.first
31
+ logger.info("The cause for the shutdown is TODO: fix SmashErrors")
32
+
33
+ pipe_to(:status_stream) do
34
+ {
35
+ instanceID: @instance_id,
36
+ type: 'status-update',
37
+ content: 'dying'
38
+ # extraInfo: blame
39
+ }
40
+ end
41
+
42
+ [:count, :wip].each do |queue|
43
+ delete_queue_message(queue, max_number_of_messages: 1)
44
+ end
45
+ send_logs_to_s3
46
+ begin
47
+ ec2.terminate_instances(dry_run: env('testing'), ids: [@instance_id])
48
+ rescue Aws::EC2::Error::DryRunOperation => e
49
+ logger.info "dry run testing in die! #{format_error_message(e)}"
50
+ @instance_id
51
+ end
52
+ end
53
+
54
+ def get_awareness!
55
+ keys = metadata_request
56
+ attr_map!(keys) { |key| metadata_request(key) }
57
+ boot_time # sets @boot_time
58
+ task_name # gets and sets @task_name
59
+ end
60
+
61
+ def task_name(ids = @instance_id)
62
+ # check tags for 'task'
63
+ if @task_name.nil?
64
+ resp = ec2.describe_instances(instance_ids: [ids].flatten)
65
+ @task_name = resp.reservations[0].instances[0].tags.select do |t|
66
+ t.value if t.key == 'taskType'
67
+ end
68
+ else
69
+ @task_name
70
+ end
71
+ end
72
+
73
+ def get_instance_url
74
+ if env('TESTING')
75
+ 'https://test-url.com'
76
+ else
77
+ hostname_uri = 'http://169.254.169.254/latest/meta-data/public-hostname'
78
+ HTTParty.get(hostname_uri).parsed_response
79
+ end
80
+ end
81
+
82
+ def metadata_request(key = '')
83
+ unless env('TESTING')
84
+ metadata_uri = "http://169.254.169.254/latest/meta-data/#{key}"
85
+ HTTParty.get(metadata_uri).parsed_response.split("\n")
86
+ else
87
+ @z ||= ['i-9254d106', 'ami-id', 'ami-launch-index', 'ami-manifest-path', 'network/thing']
88
+ if key == ''
89
+ @boogs = ['instance-id', 'ami-id', 'ami-launch-index', 'ami-manifest-path', 'network/interfaces/macs/mac/device-number']
90
+ else
91
+ @z.shift
92
+ end
93
+ end
94
+ end
95
+
96
+ def run_time
97
+ # TODO: refactor to use valid time stamps for better tracking.
98
+ # reason -> separate regions or OSs etc.
99
+ Time.now.to_i - boot_time
100
+ end
101
+
102
+ def send_frequent_status_updates(opts = {})
103
+ sleep_time = opts.delete(:interval) || 10
104
+ stream = opts.delete(:stream_name)
105
+ while true
106
+ message = lambda { |o| update_message_body(o.merge(content: status)) }
107
+ logger.info "Send update to status board #{message.call(opts)}"
108
+ pipe_to(stream || :status_stream) { message.call(opts) }
109
+ sleep sleep_time
110
+ end
111
+ end
112
+
113
+ def status(id = @instance_id)
114
+ begin
115
+ ec2.describe_instances(dry_run: env('testing'), instance_ids: [id]).
116
+ reservations[0].instances[0].state.name
117
+ rescue Aws::EC2::Errors::DryRunOperation => e
118
+ logger.info "Dry run flag set for testing: #{format_error_message(e)}"
119
+ 'testing'
120
+ end
121
+ end
122
+
123
+ def time_is_up?
124
+ # returns true when the hour mark approaches
125
+ an_hours_time = 60 * 60
126
+ five_minutes_time = 60 * 5
127
+
128
+ return false if run_time < five_minutes_time
129
+ run_time % an_hours_time < five_minutes_time
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,33 @@
1
+ require 'singleton'
2
+
3
+ module Smash
4
+ module CloudPowers
5
+ class SmashError < Exception
6
+ include Singleton
7
+
8
+ attr_reader :errors, :error_collections
9
+
10
+ ErrorCollection = Struct.new(:type, :max, :storage) do
11
+ def push!(err_msg)
12
+ storage << err_msg
13
+ throw :die if storage[type].count >= max
14
+ end
15
+ end
16
+
17
+ def build!(*args)
18
+
19
+ @errors ||= args.inject({}) do |col, t|
20
+ col.merge(t => ErrCol.new(t, 5, []))
21
+ end
22
+ end
23
+
24
+ def push_error!(type, message)
25
+ @errors[type].push!(message)
26
+ end
27
+
28
+ private
29
+ def initialize
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ require 'pathname'
2
+
3
+ module Smash
4
+ module CloudPowers
5
+ module Storage
6
+ def source_task(file)
7
+ # TODO: better path management
8
+ # byebug TODO: replace this full path business
9
+ bucket = env('task storage')
10
+ unless task_path(file).exist?
11
+ objects = s3.list_objects(bucket: bucket).contents.select do |f|
12
+ /#{Regexp.escape file}/i =~ f.key
13
+ end
14
+ objects.each do |obj|
15
+ s3.get_object(bucket: bucket, key: obj.key, response_target: task_path(file))
16
+ end
17
+ end
18
+ end
19
+
20
+ def search(bucket, pattern)
21
+ s3.list_objects(bucket: bucket).contents.select do |o|
22
+ o.key =~ pattern
23
+ end
24
+ end
25
+
26
+ def s3
27
+ @s3 ||= Aws::S3::Client.new(
28
+ region: region,
29
+ credentials: Auth.creds
30
+ )
31
+ end
32
+
33
+ def send_logs_to_s3
34
+ File.open(log_file) do |file|
35
+ s3.put_object(
36
+ bucket: log_bucket,
37
+ key: @instance_id,
38
+ body: file
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,97 @@
1
+ require 'aws-sdk'
2
+ Aws.use_bundled_cert!
3
+
4
+ module Smash
5
+ module CloudPowers
6
+ module Synapse
7
+ module Pipe
8
+ def create_stream(name)
9
+ begin
10
+ config = stream_config(stream_name: env(name))
11
+ resp = kinesis.create_stream(config)
12
+ kinesis.wait_until(:stream_exists, stream_name: config[:stream_name])
13
+ resp.successful? # (http request successful && stream created)?
14
+ rescue Exception => e
15
+ if e.kind_of? Aws::Kinesis::Errors::ResourceInUseException
16
+ logger.info "#{name} already created"
17
+ stream_status = kinesis.describe_stream(name).stream_description.stream_status
18
+ return if stream_status == 'ACTIVE'
19
+ logger.info "Not ready for traffic. Wait for 30 seconds..."
20
+ sleep 30
21
+ nil # no request -> no response
22
+ else
23
+ error_message = format_error_message(e)
24
+ logger.error error_message
25
+ errors.push_error!(:ruby, error_message) # throws: :die, :failed_job
26
+ errors[:ruby] << error_message
27
+ false # the request was not successful
28
+ end
29
+ end
30
+ end
31
+
32
+ def flow_from_pipe(stream)
33
+ throw NotImplementedError
34
+ end
35
+
36
+ def flow_to_pipe(stream)
37
+ create_stream(stream) unless stream_exists? stream
38
+ records = yield if block_given?
39
+ body = message_body_collection(records)
40
+ # TODO: this isn't working yet. figure out retry logic
41
+ # resp = kinesis.put_records(body)
42
+ # retry(lambda { stream_exists? stream }) flow_to(stream)
43
+ @last_sequence_number = resp.records.map(&:sequence_number).sort.last
44
+ # TODO: what to return? true?
45
+ end
46
+
47
+ def from_pipe(stream)
48
+ # implemented get_records and/or other consuming app stuff
49
+ throw NotImplementedError
50
+ end
51
+
52
+ def kinesis
53
+ @kinesis ||= Aws::Kinesis::Client.new(
54
+ region: region,
55
+ credentials: Auth.creds,
56
+ )
57
+ end
58
+
59
+ def message_body_collection(records)
60
+ throw NotImplementedError
61
+ end
62
+
63
+ def pipe_message_body(opts = {})
64
+ {
65
+ stream_name: env(opts[:stream_name]) || env('status_stream'),
66
+ data: opts[:data] || update_message_body(opts),
67
+ partition_key: opts[:partition_key] || @instance_id
68
+ }
69
+ end
70
+
71
+ def pipe_to(stream)
72
+ create_stream(stream) unless stream_exists? stream
73
+ message = yield if block_given?
74
+ body = update_message_body(message)
75
+ resp = kinesis.put_record pipe_message_body(stream_name: stream, data: body.to_json)
76
+ # TODO: implement retry logic for failed request
77
+ @last_sequence_number = resp.sequence_number
78
+ end
79
+
80
+ def stream_config(opts = {})
81
+ config = {
82
+ stream_name: opts[:stream_name] || env('status_stream'),
83
+ shard_count: opts[:shard_count] || 1
84
+ }
85
+ end
86
+
87
+ def stream_exists?(name)
88
+ begin
89
+ kinesis.describe_stream(stream_name: env(name))
90
+ rescue Aws::Kinesis::Errors::ResourceNotFoundException => e
91
+ false
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,112 @@
1
+ require 'uri'
2
+ require_relative '../helper'
3
+
4
+ module Smash
5
+ module CloudPowers
6
+ module Synapse
7
+ include Smash::CloudPowers::Helper
8
+
9
+ module Queue
10
+ Board = Struct.new(:set_name, :set_address, :workflow) do
11
+ include Smash::CloudPowers::Helper
12
+ def i_var
13
+ "@#{name}"
14
+ end
15
+
16
+ def address
17
+ set_address ||
18
+ env(name) ||
19
+ "https://sqs.us-west-2.amazonaws.com/088617881078/#{name}"
20
+ end
21
+
22
+ def name
23
+ set_name || address.split('/').last
24
+ end
25
+
26
+ def next_board
27
+ workflow.next
28
+ end
29
+ end # end Board
30
+ #############################################
31
+
32
+ def board_name(url)
33
+ url.to_s.split('/').last
34
+ end
35
+
36
+ # def board_name(url)
37
+ # # TODO: figure out a way to not have this and :name in Board
38
+ # # gets the name from the url
39
+ # if url =~ URI.regexp
40
+ # url = URI.parse(url)
41
+ # url.path.split('/').last.split('_').last
42
+ # else
43
+ # env(url)
44
+ # end
45
+ # end
46
+
47
+ def create_queue(name)
48
+ sqs.create_queue(queue_name: to_camel(name))
49
+ end
50
+
51
+ def delete_queue_message(queue, opts = {})
52
+ poll(queue, opts) do |msg, stats|
53
+ poller(queue).delete_message(msg)
54
+ throw :stop_polling
55
+ end
56
+ end
57
+
58
+ def get_count(board)
59
+ sqs.get_queue_attributes(
60
+ queue_url: board_name(board),
61
+ attribute_names: ['ApproximateNumberOfMessages']
62
+ ).attributes['ApproximateNumberOfMessages'].to_f
63
+ end
64
+
65
+ # Params: board<string>
66
+ # returns a message and deletes it from its origin
67
+ def pluck_message(board)
68
+ poll(board) do |msg, poller|
69
+ poller.delete_message(msg)
70
+ return msg
71
+ end
72
+ end
73
+
74
+ def poll(board, opts = {})
75
+ this_poller = poller(board)
76
+ this_poller.poll(opts) do |msg|
77
+ yield msg, this_poller if block_given?
78
+ end
79
+ end
80
+
81
+ def poller(board_name)
82
+ board = Board.new(board_name)
83
+
84
+ unless instance_variable_defined?(board.i_var)
85
+ instance_variable_set(
86
+ board.i_var,
87
+ Aws::SQS::QueuePoller.new(board.address)
88
+ )
89
+ end
90
+ instance_variable_get(board.i_var)
91
+ end
92
+
93
+ def queue_exists?(name)
94
+ sqs.list_queues(queue_name_prefix: name)
95
+ end
96
+
97
+ def send_queue_message(message, *board_info)
98
+ board = board_info.first.kind_of?(Board) ? board_info.first : Board.new(*board_info)
99
+ message = message.to_json unless message.kind_of? String
100
+ sqs.send_message(
101
+ queue_url: board.address,
102
+ message_body: message
103
+ )
104
+ end
105
+
106
+ def sqs
107
+ @sqs ||= Aws::SQS::Client.new(credentials: Auth.creds)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,12 @@
1
+ require_relative '../helper'
2
+ require_relative 'queue'
3
+ require_relative 'pipe'
4
+
5
+ module Smash
6
+ module CloudPowers
7
+ module Synapse
8
+ include Smash::CloudPowers::Synapse::Pipe
9
+ include Smash::CloudPowers::Synapse::Queue
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module CloudPowers
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,32 @@
1
+ module Smash
2
+ class Workflow
3
+ attr_accessor :all_states, :first, :last, :previous, :states
4
+
5
+ def initialize(statez = nil)
6
+ # TODO: figure out wtf here with the i-vars changing each other
7
+ @all_states = statez || [:backlog, :wip, :done]
8
+ @states = statez || [:backlog, :wip, :done]
9
+ @first = (statez || [:backlog, :wip, :done]).first
10
+ @last = (statez || [:backlog, :wip, :done]).last
11
+ @previous = [(statez || [:backlog, :wip, :done]).first]
12
+ end
13
+
14
+ def current
15
+ @states.first
16
+ end
17
+
18
+ def finished?
19
+ @states.first == @states.last
20
+ end
21
+
22
+ def next
23
+ finished ? @states.first : @states[1]
24
+ end
25
+
26
+ def next!
27
+ return if finished?
28
+ @previous << @states.shift
29
+ @states.first
30
+ end
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloud_powers
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Adam Phillipps
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-09-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: CloudPowers is a wrapper around AWS and other cloud services. This wrapper
56
+ was developed specifically for the Brain project but can be used in any other ruby
57
+ project.
58
+ email:
59
+ - adam.phillipps@gmail.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".gitignore"
65
+ - ".rspec"
66
+ - ".travis.yml"
67
+ - Gemfile
68
+ - Gemfile.lock
69
+ - LICENSE.txt
70
+ - README.md
71
+ - Rakefile
72
+ - bin/console
73
+ - bin/setup
74
+ - cloud_powers.gemspec
75
+ - lib/cloud_powers.rb
76
+ - lib/cloud_powers/auth.rb
77
+ - lib/cloud_powers/aws_resources.rb
78
+ - lib/cloud_powers/delegator.rb
79
+ - lib/cloud_powers/helper.rb
80
+ - lib/cloud_powers/self_awareness.rb
81
+ - lib/cloud_powers/smash_error.rb
82
+ - lib/cloud_powers/storage.rb
83
+ - lib/cloud_powers/synapse/pipe.rb
84
+ - lib/cloud_powers/synapse/queue.rb
85
+ - lib/cloud_powers/synapse/synapse.rb
86
+ - lib/cloud_powers/version.rb
87
+ - lib/cloud_powers/workflow.rb
88
+ homepage: https://github.com/adam-phillipps/cloud_powers
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.5.1
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Cloud providers wrapper. Currently only AWS is supported.
112
+ test_files: []