cloud_powers 0.2.7.23 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.test.env.example +6 -6
- data/.travis.yml +1 -1
- data/README +190 -0
- data/cloud_powers.gemspec +4 -4
- data/lib/cloud_powers.rb +3 -13
- data/lib/cloud_powers/aws_resources.rb +21 -4
- data/lib/cloud_powers/creatable.rb +122 -0
- data/lib/cloud_powers/helpers.rb +58 -0
- data/lib/cloud_powers/helpers/lang_help.rb +288 -0
- data/lib/cloud_powers/helpers/logic_help.rb +152 -0
- data/lib/cloud_powers/helpers/path_help.rb +90 -0
- data/lib/cloud_powers/node.rb +69 -68
- data/lib/cloud_powers/node/instance.rb +52 -0
- data/lib/cloud_powers/resource.rb +44 -0
- data/lib/cloud_powers/storage.rb +27 -14
- data/lib/{stubs → cloud_powers/stubs}/aws_stubs.rb +37 -14
- data/lib/cloud_powers/synapse/broadcast.rb +117 -0
- data/lib/cloud_powers/synapse/broadcast/channel.rb +44 -0
- data/lib/cloud_powers/synapse/pipe.rb +211 -0
- data/lib/cloud_powers/synapse/pipe/stream.rb +41 -0
- data/lib/cloud_powers/synapse/queue.rb +357 -0
- data/lib/cloud_powers/synapse/queue/board.rb +61 -95
- data/lib/cloud_powers/synapse/queue/poller.rb +29 -0
- data/lib/cloud_powers/synapse/synapse.rb +10 -12
- data/lib/cloud_powers/synapse/web_soc.rb +13 -0
- data/lib/cloud_powers/synapse/web_soc/soc_client.rb +52 -0
- data/lib/cloud_powers/synapse/web_soc/soc_server.rb +48 -0
- data/lib/cloud_powers/version.rb +1 -1
- data/lib/cloud_powers/zenv.rb +13 -12
- metadata +24 -13
- data/lib/cloud_powers/context.rb +0 -275
- data/lib/cloud_powers/delegator.rb +0 -113
- data/lib/cloud_powers/helper.rb +0 -453
- data/lib/cloud_powers/synapse/websocket/websocclient.rb +0 -53
- data/lib/cloud_powers/synapse/websocket/websocserver.rb +0 -46
- data/lib/cloud_powers/workflow_factory.rb +0 -160
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'cloud_powers/synapse/broadcast/channel'
|
2
|
+
|
3
|
+
module Smash
|
4
|
+
module CloudPowers
|
5
|
+
module Synapse
|
6
|
+
module Broadcast
|
7
|
+
include Smash::CloudPowers::Helpers
|
8
|
+
include Smash::CloudPowers::AwsResources
|
9
|
+
include Smash::CloudPowers::Zenv
|
10
|
+
|
11
|
+
# This method can be used to parse a queue name from its address.
|
12
|
+
# It can be handy if you need the name of a queue but you don't want
|
13
|
+
# the overhead of creating a QueueResource object.
|
14
|
+
#
|
15
|
+
# Parameters
|
16
|
+
# * url +String+
|
17
|
+
#
|
18
|
+
# Returns
|
19
|
+
# +String+
|
20
|
+
#
|
21
|
+
# Example
|
22
|
+
# board_name('https://sqs.us-west-53.amazonaws.com/001101010010/fooBar')
|
23
|
+
# => foo_bar_board
|
24
|
+
def channel_name(arg)
|
25
|
+
base_name = to_snake(arg.to_s.split('/').last)
|
26
|
+
%r{_channel$} =~ base_name ? base_name : "#{base_name}_channel"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates a connection point for 1..N nodes to create a connection with the Broadcast
|
30
|
+
# <b>Not Implimented</b>
|
31
|
+
#
|
32
|
+
# Parameters
|
33
|
+
# * channel +String+
|
34
|
+
#
|
35
|
+
# Notes
|
36
|
+
# This method is not implemented yet (V 0.2.7)
|
37
|
+
def create_distributor(channel)
|
38
|
+
sns.create_application_platform()
|
39
|
+
end
|
40
|
+
|
41
|
+
# Creates a point to connect to for information about a given topic
|
42
|
+
#
|
43
|
+
# Parameters
|
44
|
+
# * name +String+ - the name of the Channel/Topic to be created
|
45
|
+
#
|
46
|
+
# Returns
|
47
|
+
# +Broadcast::Channel+ - representing the created channel
|
48
|
+
def create_channel(name, **config)
|
49
|
+
channel_resource =
|
50
|
+
Smash::CloudPowers::Synapse::Broadcast::Channel.create!(
|
51
|
+
name: name, client: sns, **config
|
52
|
+
)
|
53
|
+
|
54
|
+
self.attr_map(channel_resource.call_name => channel_resource) do |attribute, resource|
|
55
|
+
instance_attr_accessor attribute
|
56
|
+
resource
|
57
|
+
end
|
58
|
+
|
59
|
+
channel_resource
|
60
|
+
end
|
61
|
+
|
62
|
+
# Deletes a topic from SNS-land
|
63
|
+
#
|
64
|
+
# Parameters
|
65
|
+
# * channel <Broadcast::Channel>
|
66
|
+
def delete_channel!(channel)
|
67
|
+
sns.delete_topic(topic_arn: channel.arn)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Creates a connection to the Broadcast so that new messages will be picked up
|
71
|
+
#
|
72
|
+
# Parameters channel <Broadcast::Channel>
|
73
|
+
def listen_on(channel)
|
74
|
+
sns.subscribe(
|
75
|
+
topic_arn: channel.arn,
|
76
|
+
protocol: 'application',
|
77
|
+
endpoint: channel.endpoint
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Lists the created topics in SNS.
|
82
|
+
#
|
83
|
+
# Returns results <Array
|
84
|
+
def real_channels
|
85
|
+
results = []
|
86
|
+
next_token = ''
|
87
|
+
loop do
|
88
|
+
resp = sns.list_topics((next_token.empty? ? {} : { next_token: next_token }))
|
89
|
+
results.concat(resp.topics.map(&:topic_arn))
|
90
|
+
next_token = (resp.next_token.empty? ? '' : resp.next_token)
|
91
|
+
break if next_token.empty?
|
92
|
+
end
|
93
|
+
results
|
94
|
+
end
|
95
|
+
|
96
|
+
# Send a message to a Channel using SNS#publish
|
97
|
+
#
|
98
|
+
# Parameters
|
99
|
+
# * opts +Hash+ - this includes all the keys AWS uses but for now it only has defaults
|
100
|
+
# for topic_arn and the message
|
101
|
+
# * * +:topic_arn+ - the ARN for the topic in AWS
|
102
|
+
# * * +:message+ - the message that should be broadcasted to whoever is listening on this
|
103
|
+
# Channel (AWS Topic)
|
104
|
+
def send_broadcast(opts = {})
|
105
|
+
msg = opts.delete(:message) || ""
|
106
|
+
|
107
|
+
package = {
|
108
|
+
topic_arn: "topicARN",
|
109
|
+
message: msg.to_json
|
110
|
+
}.merge(opts)
|
111
|
+
|
112
|
+
sns.publish(package)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'cloud_powers/resource'
|
2
|
+
|
3
|
+
module Smash
|
4
|
+
module CloudPowers
|
5
|
+
module Synapse
|
6
|
+
module Broadcast
|
7
|
+
class Channel < Smash::CloudPowers::Resource
|
8
|
+
|
9
|
+
attr_accessor :sns
|
10
|
+
|
11
|
+
def initialize(name:, client: sns, **config)
|
12
|
+
@sns = client
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
# Prefers the given arn but it can make a best guess if none is given
|
17
|
+
#
|
18
|
+
# Returns
|
19
|
+
# arn +String+ - arn for this resource
|
20
|
+
def arn
|
21
|
+
@remote_id || "arn:aws:sns:#{zfind(:region)}:#{zfind(:accound_number)}:#{name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def remote_id
|
25
|
+
@remote_id || arn
|
26
|
+
end
|
27
|
+
|
28
|
+
# Prefers the given name but it can parse the arn to find one
|
29
|
+
#
|
30
|
+
# Returns
|
31
|
+
# name +String+ - name for this resource
|
32
|
+
def name
|
33
|
+
@name || set_arn.split(':').last
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_resource
|
37
|
+
@response = sns.create_topic(name: name)
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'cloud_powers/synapse/pipe/stream'
|
2
|
+
|
3
|
+
module Smash
|
4
|
+
module CloudPowers
|
5
|
+
module Synapse
|
6
|
+
module Pipe
|
7
|
+
include Smash::CloudPowers::AwsResources
|
8
|
+
include Smash::CloudPowers::Helpers
|
9
|
+
include Smash::CloudPowers::Zenv
|
10
|
+
|
11
|
+
def build_pipe(name:, type: :stream, **config)
|
12
|
+
build_method_name = "build_#{type}"
|
13
|
+
if self.respond_to? build_method_name
|
14
|
+
self.public_send build_method_name, name: name, **config
|
15
|
+
else
|
16
|
+
build_stream(name: name, **config)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_pipe(name:, type: :stream, **config)
|
21
|
+
create_method_name = "create_#{type}"
|
22
|
+
if self.respond_to? create_method_name
|
23
|
+
self.public_send create_method_name, name: name, **config
|
24
|
+
else
|
25
|
+
create_stream(name: name, **config)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create a Kinesis stream or wait until the stream with the given name is
|
30
|
+
# through being created.
|
31
|
+
#
|
32
|
+
# Parameters
|
33
|
+
# * name +String+
|
34
|
+
#
|
35
|
+
# Returns Boolean or nil
|
36
|
+
# * returns true or false if the request was successful or not
|
37
|
+
# * returns true if the stream has already been created
|
38
|
+
# * returns false if the stream was not created
|
39
|
+
def build_stream(name:, client: kinesis, **config)
|
40
|
+
stream_resource = Smash::CloudPowers::Synapse::Pipe::Stream.build(
|
41
|
+
name: name, client: client, **config
|
42
|
+
)
|
43
|
+
|
44
|
+
self.attr_map(stream_resource.call_name => stream_resource) do |attribute, resource|
|
45
|
+
instance_attr_accessor attribute
|
46
|
+
resource
|
47
|
+
end
|
48
|
+
|
49
|
+
stream_resource
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_stream(name:, client: kinesis, **config)
|
53
|
+
stream_resource =
|
54
|
+
Smash::CloudPowers::Synapse::Pipe::Stream.create!(name: name, client: client, **config)
|
55
|
+
|
56
|
+
self.attr_map(stream_resource.call_name => stream_resource) do |attribute, resource|
|
57
|
+
instance_attr_accessor attribute
|
58
|
+
resource
|
59
|
+
end
|
60
|
+
|
61
|
+
stream_resource
|
62
|
+
end
|
63
|
+
# Use the KCL and LangDaemon to read from a stream
|
64
|
+
#
|
65
|
+
# Parameters stream String
|
66
|
+
#
|
67
|
+
# Notes
|
68
|
+
# * This method is not implemented yet (V 0.2.7)
|
69
|
+
def flow_from_pipe(stream)
|
70
|
+
throw NotImplementedError
|
71
|
+
end
|
72
|
+
|
73
|
+
# Sends data through a Pipe. This method is used for lower throughput
|
74
|
+
# applications, e.g. logging, status updates
|
75
|
+
#
|
76
|
+
# Parameters
|
77
|
+
# * stream +String+
|
78
|
+
#
|
79
|
+
# Returns
|
80
|
+
# @last_sequence_number +String+
|
81
|
+
#
|
82
|
+
# Notes
|
83
|
+
# * This method is not implemented yet (V 0.2.7)
|
84
|
+
def flow_to_pipe(stream)
|
85
|
+
throw NotImplementedError
|
86
|
+
create_stream(stream) unless stream_exists? stream
|
87
|
+
records = yield if block_given?
|
88
|
+
body = message_body_collection(records)
|
89
|
+
puts body
|
90
|
+
# TODO: this isn't working yet. figure out retry logic
|
91
|
+
# resp = kinesis.put_records(body)
|
92
|
+
# retry(lambda { stream_exists? stream }) flow_to(stream)
|
93
|
+
@last_sequence_number = resp.records.map(&:sequence_number).sort.last
|
94
|
+
# TODO: what to return? true?
|
95
|
+
end
|
96
|
+
|
97
|
+
# Read messages from the Pipe without using the KCL
|
98
|
+
#
|
99
|
+
# Parameters stream String
|
100
|
+
#
|
101
|
+
# Notes
|
102
|
+
# * This method is not implemented yet (V 0.2.7)
|
103
|
+
def from_pipe(stream)
|
104
|
+
# implement get_records and/or other consuming app stuff
|
105
|
+
throw NotImplementedError
|
106
|
+
end
|
107
|
+
|
108
|
+
# This message will prepare a set of collections to be sent through the Pipe
|
109
|
+
#
|
110
|
+
# Parameters
|
111
|
+
# * records
|
112
|
+
#
|
113
|
+
# Notes
|
114
|
+
# * This method is not implemented yet (V 0.2.7)
|
115
|
+
def message_body_collection(records)
|
116
|
+
throw NotImplementedError
|
117
|
+
end
|
118
|
+
|
119
|
+
# Default message package. This method yields the basic configuration
|
120
|
+
# and message body for a stream and all options can be changed.
|
121
|
+
#
|
122
|
+
# Parameters opts Hash (optional)
|
123
|
+
# * stream_name: String name of the stream to pipe to
|
124
|
+
# * data: String message to send
|
125
|
+
# * partition_key: String defaults to @instance_id
|
126
|
+
#
|
127
|
+
# Returns
|
128
|
+
# +Hash+
|
129
|
+
#
|
130
|
+
# Notes
|
131
|
+
# * See +#zfind()+
|
132
|
+
# * See +#instance_id()+
|
133
|
+
# * See +#update_message_body()+
|
134
|
+
def pipe_message_body(opts = {})
|
135
|
+
{
|
136
|
+
stream_name: zfind(opts[:stream_name]) || zfind('status_stream'),
|
137
|
+
data: opts[:data] || update_message_body(opts),
|
138
|
+
partition_key: opts[:partition_key] || @instance_id || 'unk'
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
# Use Kinesis streams to send a message. The message is given to the method
|
143
|
+
# through a block that gets passed to the method.
|
144
|
+
#
|
145
|
+
# Parameters
|
146
|
+
# * stream +String+
|
147
|
+
# * block - a block that generates a string that will be used in the message body
|
148
|
+
#
|
149
|
+
# Returns
|
150
|
+
# the sequence_number from the sent message.
|
151
|
+
#
|
152
|
+
# Example
|
153
|
+
# pipe_to(:status_stream) do
|
154
|
+
# # the return from the inner method is what is sent
|
155
|
+
# do_some_stuff_to_generate_a_message()
|
156
|
+
# end
|
157
|
+
def pipe_to(stream)
|
158
|
+
message = ''
|
159
|
+
create_stream() unless stream_exists?(stream)
|
160
|
+
message = yield if block_given?
|
161
|
+
body = update_message_body(message)
|
162
|
+
resp = kinesis.put_record pipe_message_body(stream_name: stream, data: body.to_json)
|
163
|
+
# TODO: implement retry logic for failed request
|
164
|
+
@last_sequence_number = resp.sequence_number
|
165
|
+
end
|
166
|
+
|
167
|
+
# New stream config with sensible defaults
|
168
|
+
#
|
169
|
+
# Parameters
|
170
|
+
# * opts +Hash+ (optional)
|
171
|
+
# * * stream_name - the name to give the stream
|
172
|
+
# * * shard_count - the number of shards to create
|
173
|
+
def stream_config(opts = {})
|
174
|
+
{
|
175
|
+
stream_name: opts[:stream_name] || zfind(:status_stream),
|
176
|
+
shard_count: opts[:shard_count] || 1
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
# Find out if the stream already exists.
|
181
|
+
#
|
182
|
+
# Parameters
|
183
|
+
# * name +String+
|
184
|
+
#
|
185
|
+
# Returns
|
186
|
+
# +Boolean+
|
187
|
+
def stream_exists?(name)
|
188
|
+
return true unless zfind(name).nil?
|
189
|
+
|
190
|
+
begin
|
191
|
+
kinesis.describe_stream(stream_name: name)
|
192
|
+
true
|
193
|
+
rescue Aws::Kinesis::Errors::ResourceNotFoundException
|
194
|
+
false
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Get the status name for this stream
|
199
|
+
#
|
200
|
+
# Parameters
|
201
|
+
# *name +String+
|
202
|
+
#
|
203
|
+
# Returns
|
204
|
+
# +String+ - stream status, one of: CREATING, DELETING, ACTIVE or UPDATING
|
205
|
+
def pipe_status(name)
|
206
|
+
kinesis.describe_stream(stream_name: name).stream_description.stream_status
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Smash
|
2
|
+
module CloudPowers
|
3
|
+
module Synapse
|
4
|
+
module Pipe
|
5
|
+
class Stream < Smash::CloudPowers::Resource
|
6
|
+
|
7
|
+
attr_accessor :kinesis, :shard_count
|
8
|
+
|
9
|
+
def initialize(name:, client: kinesis, **config)
|
10
|
+
super
|
11
|
+
@kinesis = client
|
12
|
+
@shard_count = config[:shard_count] || 1
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_resource
|
16
|
+
begin
|
17
|
+
@response = kinesis.create_stream(config)
|
18
|
+
kinesis.wait_until(:stream_exists, stream_name: config[:stream_name])
|
19
|
+
@response.successful? # (http request successful && stream created)?
|
20
|
+
rescue Exception => e
|
21
|
+
if e.kind_of? Aws::Kinesis::Errors::ResourceInUseException
|
22
|
+
logger.info "#{name} already created"
|
23
|
+
return if stream_status == 'ACTIVE'
|
24
|
+
logger.info "Not ready for traffic. Wait for 30 seconds..."
|
25
|
+
sleep 1
|
26
|
+
@saved = true # acts like it would if it had to create the stream
|
27
|
+
@linked = true
|
28
|
+
else
|
29
|
+
raise
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def config
|
35
|
+
{ stream_name: @name, shard_count: @shard_count }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,357 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'cloud_powers/synapse/queue/board'
|
3
|
+
|
4
|
+
module Smash
|
5
|
+
module CloudPowers
|
6
|
+
module Synapse
|
7
|
+
module Queue
|
8
|
+
include Smash::CloudPowers::AwsResources
|
9
|
+
include Smash::CloudPowers::Helpers
|
10
|
+
|
11
|
+
# A simple Struct that acts as a Name to URL map
|
12
|
+
#
|
13
|
+
# Parameters
|
14
|
+
# * :set_name +String+ (optional) - An optional name. It should be the same name as the
|
15
|
+
# the QueueResource and/or Queue you're working with, or else this Struct isn't that useful
|
16
|
+
# * :set_url +String+ (optional) - An optional URL. It should be the same URL as the
|
17
|
+
# the QueueResource and/or Queue you're working with, or else this Struct isn't that useful
|
18
|
+
#
|
19
|
+
# Attributes
|
20
|
+
# * name +String+ - the +:set_name+ or parse the +#address()+ for the name
|
21
|
+
# * url +String+ - the +:set_url+ or add the name to the end of a best guess at the URL
|
22
|
+
#
|
23
|
+
# Example
|
24
|
+
# name_url_map = NUMap.new(nil, 'https://sqs.us-west-53.amazonaws.com/001101010010/fooBar')
|
25
|
+
# name_url_map.name
|
26
|
+
# # => 'fooBar'
|
27
|
+
#
|
28
|
+
# # and now in reverse
|
29
|
+
# url_name_map = NUMap.new('snargleBargle')
|
30
|
+
# url_name_map.address
|
31
|
+
# # => 'https://sqs.us-west-53.amazonaws.com/001101010010/snargleBargle'
|
32
|
+
NUMap = Struct.new(:set_name, :set_url) do
|
33
|
+
# Gives you back the name, even if it hasn't been set
|
34
|
+
#
|
35
|
+
# Returns
|
36
|
+
# +String+
|
37
|
+
def name
|
38
|
+
set_name || url.split('/').last # Queue names are last in the URL path
|
39
|
+
end
|
40
|
+
|
41
|
+
# Gives you back the URL, even if it hasn't been set
|
42
|
+
#
|
43
|
+
# Returns
|
44
|
+
# +String+
|
45
|
+
def url
|
46
|
+
set_url || Smash::CloudPowers::Synapse::Queue::Board.new(name: name).best_guess_address
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Gives a best guess at the URL that points toward this Resource's Queue. It uses a couple params
|
51
|
+
# to build a standard URL for SQS. The only problem with using this last resort is you may need
|
52
|
+
# to use a Queue from a different region, account or name but it can be a handy catch-all for the URLs
|
53
|
+
# for most cases.
|
54
|
+
#
|
55
|
+
# Returns
|
56
|
+
# +String+
|
57
|
+
#
|
58
|
+
# Example
|
59
|
+
# best_guess_address('fooBar')
|
60
|
+
# => "https://sqs.us-west-2.amazonaws.com/12345678/fooBar"
|
61
|
+
#
|
62
|
+
# Notes
|
63
|
+
# * See <tt>Smash::CloudPowers::Zenv#zfind()</tt> to understand how
|
64
|
+
# this method finds your region and account number
|
65
|
+
def best_guess_address(board_name = @name)
|
66
|
+
"https://sqs.#{zfind(:aws_region)}.amazonaws.com/#{zfind(:account_number)}/#{board_name}"
|
67
|
+
end
|
68
|
+
|
69
|
+
# This method can be used to parse a queue name from its address.
|
70
|
+
# It can be handy if you need the name of a queue but you don't want
|
71
|
+
# the overhead of creating a QueueResource object.
|
72
|
+
#
|
73
|
+
# Parameters
|
74
|
+
# * url +String+
|
75
|
+
#
|
76
|
+
# Returns
|
77
|
+
# +String+
|
78
|
+
#
|
79
|
+
# Example
|
80
|
+
# board_name('https://sqs.us-west-53.amazonaws.com/001101010010/fooBar')
|
81
|
+
# => foo_bar_board
|
82
|
+
def board_name(arg)
|
83
|
+
base_name = to_snake(arg.to_s.split('/').last)
|
84
|
+
%r{_board$} =~ base_name ? base_name : "#{base_name}_board"
|
85
|
+
end
|
86
|
+
|
87
|
+
def build_queue(name:, type: :board, **config)
|
88
|
+
build_method_name = "build_#{type}"
|
89
|
+
if self.respond_to? build_method_name
|
90
|
+
self.public_send build_method_name, name: name, **config
|
91
|
+
else
|
92
|
+
build_board(name: name, **config)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def create_queue(name:, type: :board, **config)
|
97
|
+
create_method_name = "build_#{type}"
|
98
|
+
if self.respond_to? create_method_name
|
99
|
+
self.public_send create_method_name, name: name, **config
|
100
|
+
else
|
101
|
+
create_queue(name: name, **config)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# This method builds a Queue::QueueResource object for you to use but doesn't
|
106
|
+
# invoke the <tt>#create!()</tt> method, so no API call is made to create the Queue
|
107
|
+
# on SQS. This can be used if the QueueResource and/or Queue already exists.
|
108
|
+
#
|
109
|
+
# Parameters
|
110
|
+
# * name +String+ - name of the Queue you want to interact with
|
111
|
+
#
|
112
|
+
# Returns
|
113
|
+
# Queue::QueueResource
|
114
|
+
#
|
115
|
+
# Example
|
116
|
+
# queue_object = build_queue('exampleQueue')
|
117
|
+
# queue_object.address
|
118
|
+
# => https://sqs.us-west-2.amazonaws.com/81234567/exampleQueue
|
119
|
+
def build_board(name:, client: sqs, **config)
|
120
|
+
board_resource = Smash::CloudPowers::Synapse::Queue::Board.build(
|
121
|
+
name: to_camel(name), client: sqs
|
122
|
+
)
|
123
|
+
|
124
|
+
attr_map(board_resource.call_name => board_resource) do |attribute, resource|
|
125
|
+
instance_attr_accessor attribute
|
126
|
+
resource
|
127
|
+
end
|
128
|
+
|
129
|
+
board_resource
|
130
|
+
end
|
131
|
+
|
132
|
+
# This method allows you to create a queue on SQS without explicitly creating a QueueResource object
|
133
|
+
#
|
134
|
+
# Parameters
|
135
|
+
# * name +String+ - The name of the Queue to be created
|
136
|
+
#
|
137
|
+
# Returns
|
138
|
+
# Queue::QueueResource
|
139
|
+
#
|
140
|
+
# Example
|
141
|
+
# create_queue('exampleQueue')
|
142
|
+
# get_queue_message_count
|
143
|
+
def create_board(name:, client: sqs, **config)
|
144
|
+
board_resource = Smash::CloudPowers::Synapse::Queue::Board.create!(
|
145
|
+
name: to_camel(name), client: sqs
|
146
|
+
)
|
147
|
+
|
148
|
+
attr_map(board_resource.call_name => board_resource) do |attribute, resource|
|
149
|
+
instance_attr_accessor attribute
|
150
|
+
resource
|
151
|
+
end
|
152
|
+
|
153
|
+
board_resource
|
154
|
+
end
|
155
|
+
|
156
|
+
# Deletes a queue message without caring about reading/interacting with the message.
|
157
|
+
# This is usually used for progress tracking, ie; a Neuron takes a message from the Backlog, moves it to
|
158
|
+
# WIP and deletes it from Backlog. Then repeats these steps for the remaining States in the Workflow
|
159
|
+
#
|
160
|
+
# Parameters
|
161
|
+
# * queue +String+ - queue is the name of the +Queue+ to interact with
|
162
|
+
# * opts +Hash+ (optional) - a configuration +Hash+ for the +SQS::QueuePoller+
|
163
|
+
#
|
164
|
+
# Notes
|
165
|
+
# * throws :stop_polling after the message is deleted
|
166
|
+
#
|
167
|
+
# Example
|
168
|
+
# get_queue_message_count('exampleQueue')
|
169
|
+
# # => n
|
170
|
+
# delete_queue_message('exampleQueue')
|
171
|
+
# get_queue_message_count('exampleQueue')
|
172
|
+
# # => n-1
|
173
|
+
def delete_queue_message(queue, opts = {})
|
174
|
+
poll(queue, opts) do |msg, stats|
|
175
|
+
poller(queue).delete_message(msg)
|
176
|
+
throw :stop_polling
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# This method is used to get the approximate count of messages in a given queue
|
181
|
+
#
|
182
|
+
# Parameters
|
183
|
+
# * resource_url +String+ - the URL for the resource you need to get a count from
|
184
|
+
#
|
185
|
+
# Returns
|
186
|
+
# +Float+
|
187
|
+
#
|
188
|
+
# Example
|
189
|
+
# get_queue_message_count('exampleQueue')
|
190
|
+
# # => n
|
191
|
+
# delete_queue_message('exampleQueue')
|
192
|
+
# get_queue_message_count('exampleQueue')
|
193
|
+
# # => n-1
|
194
|
+
def get_queue_message_count(resource_url)
|
195
|
+
sqs.get_queue_attributes(
|
196
|
+
queue_url: resource_url,
|
197
|
+
attribute_names: ['ApproximateNumberOfMessages']
|
198
|
+
).attributes['ApproximateNumberOfMessages'].to_f
|
199
|
+
end
|
200
|
+
|
201
|
+
# Get an Aws::SQS::QueuePoller for a a queue to deal with messages
|
202
|
+
#
|
203
|
+
# Parameters
|
204
|
+
# * name +String+ - name of the queue. this also becomes the name of
|
205
|
+
# the +Poller+ object
|
206
|
+
# * client <tt>Aws::SQS::Client</tt> (optional) - good for hitting
|
207
|
+
# different regions or even stubbing for testing
|
208
|
+
def get_queue_poller(name, client = sqs)
|
209
|
+
queue_poller(url: best_guess_address(name), client: client)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Get a message from a Queue
|
213
|
+
#
|
214
|
+
# Parameters
|
215
|
+
# * resource<String|symbol>: The name of the resource
|
216
|
+
#
|
217
|
+
# Returns
|
218
|
+
# * +String+ if +msg.body+ is not valid JSON
|
219
|
+
# * +Hash+ if +msg.body+ is valid JSON
|
220
|
+
#
|
221
|
+
# Example
|
222
|
+
# # msg.body == 'Hey' # +String+
|
223
|
+
# pluck_queue_message('exampleQueue')
|
224
|
+
# # => 'Hey' # +String+
|
225
|
+
#
|
226
|
+
# # msg.body == "\{"tally":"ho"\}" # +JSON+
|
227
|
+
# pluck_queue_message('exampleQueue')
|
228
|
+
# # => { 'tally' => 'ho' } # +Hash+
|
229
|
+
def pluck_queue_message(resource)
|
230
|
+
poll(resource) do |msg, poller|
|
231
|
+
poller.delete_message(msg)
|
232
|
+
return valid_json?(msg.body) ? from_json(msg.body) : msg.body
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Polls the given resource with the given options hash and a block that interacts with
|
237
|
+
# the message that is retrieved from the queue
|
238
|
+
#
|
239
|
+
# Parameters
|
240
|
+
# * :queue_name +String+ - the name of the queue that you want to poll
|
241
|
+
# * :sqs +AWS::SQS:QueuePoller+ polling configuration option(s)
|
242
|
+
# * +block+ is the block that is used to interact with the message that was retrieved
|
243
|
+
#
|
244
|
+
# Returns
|
245
|
+
# the results from the +message+ and the +block+ that interacts with the +message(s)+
|
246
|
+
#
|
247
|
+
# Example
|
248
|
+
# # continuously run jobs from messages in the Queue and leaves the message in the queue
|
249
|
+
# # using the +:skip_delete+ parameter
|
250
|
+
# poll(:backlog, :skip_delete) do |msg|
|
251
|
+
# demo_job = Job.new(msg.body)
|
252
|
+
# demo_job.run
|
253
|
+
# end
|
254
|
+
def poll(queue_name, client: sqs, poller: get_queue_poller(queue_name), **config)
|
255
|
+
results = nil
|
256
|
+
poller.poll(config) do |msg|
|
257
|
+
results = yield msg, poller if block_given?
|
258
|
+
poller.delete_message(msg)
|
259
|
+
throw :stop_polling
|
260
|
+
end
|
261
|
+
results
|
262
|
+
end
|
263
|
+
|
264
|
+
# Checks SQS for the existence of this queue using the <tt>#queue_search()</tt> method
|
265
|
+
#
|
266
|
+
# Parameters
|
267
|
+
# * name +String+
|
268
|
+
#
|
269
|
+
# Returns
|
270
|
+
# Boolean
|
271
|
+
#
|
272
|
+
# Notes
|
273
|
+
# * See <tt>#queue_search()</tt>
|
274
|
+
def queue_exists?(name)
|
275
|
+
!queue_search(name).empty?
|
276
|
+
end
|
277
|
+
|
278
|
+
# This method can be used to parse a queue name from its address.
|
279
|
+
# It can be handy if you need the name of a queue but you don't want
|
280
|
+
# the overhead of creating a QueueResource object.
|
281
|
+
#
|
282
|
+
# Parameters
|
283
|
+
# * url +String+
|
284
|
+
#
|
285
|
+
# Returns
|
286
|
+
# +String+
|
287
|
+
#
|
288
|
+
# Example
|
289
|
+
# resource_name('https://sqs.us-west-53.amazonaws.com/001101010010/fooBar')
|
290
|
+
# => fooBar
|
291
|
+
def queue_name(arg)
|
292
|
+
base_name = to_snake(arg.to_s.split('/').last)
|
293
|
+
%r{_queue$} =~ base_name ? base_name : "#{base_name}_queue"
|
294
|
+
end
|
295
|
+
|
296
|
+
# This method can be used to parse a queue poller name from its queue
|
297
|
+
# name or <tt>@url</tt>. It can be handy if you need the name of a
|
298
|
+
# queue but you don't want the overhead of creating a QueueResource object.
|
299
|
+
#
|
300
|
+
# Parameters
|
301
|
+
# * url +String+
|
302
|
+
#
|
303
|
+
# Returns
|
304
|
+
# +String+
|
305
|
+
#
|
306
|
+
# Example
|
307
|
+
# resource_name('https://sqs.us-west-53.amazonaws.com/001101010010/fooBar')
|
308
|
+
# => fooBar
|
309
|
+
def queue_poller_name(arg)
|
310
|
+
base_name = to_snake(arg.to_s.split('/').last)
|
311
|
+
%r{_queue_poller$} =~ base_name ? base_name : "#{base_name}_queue_poller"
|
312
|
+
end
|
313
|
+
|
314
|
+
# Searches for a queue based on the name
|
315
|
+
#
|
316
|
+
# Parameters
|
317
|
+
# name +String+
|
318
|
+
#
|
319
|
+
# Returns
|
320
|
+
# queue_urls +String+
|
321
|
+
#
|
322
|
+
# Example
|
323
|
+
# results = queue_search('exampleQueue') # returns related URLs
|
324
|
+
# results.first =~ /exampleQueue/ # regex match against the URL
|
325
|
+
def queue_search(name)
|
326
|
+
urls = sqs.list_queues(queue_name_prefix: name).queue_urls
|
327
|
+
urls.map do |url|
|
328
|
+
build_board(name: queue_name(url), client: sqs) do |board|
|
329
|
+
board.instance_attr_accessor :url
|
330
|
+
board.url = url
|
331
|
+
board
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Sends a given message to a given queue
|
337
|
+
#
|
338
|
+
# Parameters
|
339
|
+
# * address +String+ - address of the Queue you want to interact with
|
340
|
+
# * message +String+ - message to be sent
|
341
|
+
#
|
342
|
+
# Returns
|
343
|
+
# <tt>Array<String></tt> - Array of URLs
|
344
|
+
#
|
345
|
+
# Example
|
346
|
+
# legit_address = 'https://sqs.us-west-2.amazonaws.com/12345678/exampleQueue'
|
347
|
+
# random_message = 'Wowza, this is pretty easy.'
|
348
|
+
# resp = send_queue_message(legit_address, random_message))
|
349
|
+
# resp.message_id
|
350
|
+
# => 'some message id'
|
351
|
+
def send_queue_message(address, message, this_sqs = sqs)
|
352
|
+
this_sqs.send_message(queue_url: address, message_body: message)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|