cloud_powers 0.2.7.23 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|