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.
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