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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.test.env.example +6 -6
  4. data/.travis.yml +1 -1
  5. data/README +190 -0
  6. data/cloud_powers.gemspec +4 -4
  7. data/lib/cloud_powers.rb +3 -13
  8. data/lib/cloud_powers/aws_resources.rb +21 -4
  9. data/lib/cloud_powers/creatable.rb +122 -0
  10. data/lib/cloud_powers/helpers.rb +58 -0
  11. data/lib/cloud_powers/helpers/lang_help.rb +288 -0
  12. data/lib/cloud_powers/helpers/logic_help.rb +152 -0
  13. data/lib/cloud_powers/helpers/path_help.rb +90 -0
  14. data/lib/cloud_powers/node.rb +69 -68
  15. data/lib/cloud_powers/node/instance.rb +52 -0
  16. data/lib/cloud_powers/resource.rb +44 -0
  17. data/lib/cloud_powers/storage.rb +27 -14
  18. data/lib/{stubs → cloud_powers/stubs}/aws_stubs.rb +37 -14
  19. data/lib/cloud_powers/synapse/broadcast.rb +117 -0
  20. data/lib/cloud_powers/synapse/broadcast/channel.rb +44 -0
  21. data/lib/cloud_powers/synapse/pipe.rb +211 -0
  22. data/lib/cloud_powers/synapse/pipe/stream.rb +41 -0
  23. data/lib/cloud_powers/synapse/queue.rb +357 -0
  24. data/lib/cloud_powers/synapse/queue/board.rb +61 -95
  25. data/lib/cloud_powers/synapse/queue/poller.rb +29 -0
  26. data/lib/cloud_powers/synapse/synapse.rb +10 -12
  27. data/lib/cloud_powers/synapse/web_soc.rb +13 -0
  28. data/lib/cloud_powers/synapse/web_soc/soc_client.rb +52 -0
  29. data/lib/cloud_powers/synapse/web_soc/soc_server.rb +48 -0
  30. data/lib/cloud_powers/version.rb +1 -1
  31. data/lib/cloud_powers/zenv.rb +13 -12
  32. metadata +24 -13
  33. data/lib/cloud_powers/context.rb +0 -275
  34. data/lib/cloud_powers/delegator.rb +0 -113
  35. data/lib/cloud_powers/helper.rb +0 -453
  36. data/lib/cloud_powers/synapse/websocket/websocclient.rb +0 -53
  37. data/lib/cloud_powers/synapse/websocket/websocserver.rb +0 -46
  38. 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