cloudenvoy 0.1.0.dev → 0.4.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/.github/workflows/test.yml +41 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +1 -0
- data/Appraisals +25 -0
- data/CHANGELOG.md +32 -0
- data/Gemfile.lock +215 -1
- data/README.md +581 -7
- data/app/controllers/cloudenvoy/application_controller.rb +8 -0
- data/app/controllers/cloudenvoy/subscriber_controller.rb +59 -0
- data/cloudenvoy.gemspec +15 -2
- data/config/routes.rb +5 -0
- data/examples/rails/.ruby-version +1 -0
- data/examples/rails/Gemfile +15 -0
- data/examples/rails/Gemfile.lock +207 -0
- data/examples/rails/Procfile +1 -0
- data/examples/rails/README.md +31 -0
- data/examples/rails/Rakefile +8 -0
- data/examples/rails/app/assets/config/manifest.js +2 -0
- data/examples/rails/app/assets/images/.keep +0 -0
- data/examples/rails/app/assets/stylesheets/application.css +15 -0
- data/examples/rails/app/channels/application_cable/channel.rb +6 -0
- data/examples/rails/app/channels/application_cable/connection.rb +6 -0
- data/examples/rails/app/controllers/application_controller.rb +4 -0
- data/examples/rails/app/controllers/concerns/.keep +0 -0
- data/examples/rails/app/helpers/application_helper.rb +4 -0
- data/examples/rails/app/javascript/packs/application.js +15 -0
- data/examples/rails/app/jobs/application_job.rb +9 -0
- data/examples/rails/app/mailers/application_mailer.rb +6 -0
- data/examples/rails/app/models/application_record.rb +5 -0
- data/examples/rails/app/models/concerns/.keep +0 -0
- data/examples/rails/app/publishers/hello_publisher.rb +34 -0
- data/examples/rails/app/subscribers/hello_subscriber.rb +16 -0
- data/examples/rails/app/views/layouts/application.html.erb +14 -0
- data/examples/rails/app/views/layouts/mailer.html.erb +13 -0
- data/examples/rails/app/views/layouts/mailer.text.erb +1 -0
- data/examples/rails/bin/rails +6 -0
- data/examples/rails/bin/rake +6 -0
- data/examples/rails/bin/setup +35 -0
- data/examples/rails/config.ru +7 -0
- data/examples/rails/config/application.rb +19 -0
- data/examples/rails/config/boot.rb +7 -0
- data/examples/rails/config/cable.yml +10 -0
- data/examples/rails/config/credentials.yml.enc +1 -0
- data/examples/rails/config/database.yml +25 -0
- data/examples/rails/config/environment.rb +7 -0
- data/examples/rails/config/environments/development.rb +65 -0
- data/examples/rails/config/environments/production.rb +114 -0
- data/examples/rails/config/environments/test.rb +50 -0
- data/examples/rails/config/initializers/application_controller_renderer.rb +9 -0
- data/examples/rails/config/initializers/assets.rb +14 -0
- data/examples/rails/config/initializers/backtrace_silencers.rb +8 -0
- data/examples/rails/config/initializers/cloudenvoy.rb +22 -0
- data/examples/rails/config/initializers/content_security_policy.rb +29 -0
- data/examples/rails/config/initializers/cookies_serializer.rb +7 -0
- data/examples/rails/config/initializers/filter_parameter_logging.rb +6 -0
- data/examples/rails/config/initializers/inflections.rb +17 -0
- data/examples/rails/config/initializers/mime_types.rb +5 -0
- data/examples/rails/config/initializers/wrap_parameters.rb +16 -0
- data/examples/rails/config/locales/en.yml +33 -0
- data/examples/rails/config/master.key +1 -0
- data/examples/rails/config/puma.rb +37 -0
- data/examples/rails/config/routes.rb +4 -0
- data/examples/rails/config/spring.rb +8 -0
- data/examples/rails/config/storage.yml +34 -0
- data/examples/rails/db/development.sqlite3 +0 -0
- data/examples/rails/db/test.sqlite3 +0 -0
- data/examples/rails/lib/assets/.keep +0 -0
- data/examples/rails/log/.keep +0 -0
- data/examples/rails/public/404.html +67 -0
- data/examples/rails/public/422.html +67 -0
- data/examples/rails/public/500.html +66 -0
- data/examples/rails/public/apple-touch-icon-precomposed.png +0 -0
- data/examples/rails/public/apple-touch-icon.png +0 -0
- data/examples/rails/public/favicon.ico +0 -0
- data/examples/rails/storage/.keep +0 -0
- data/gemfiles/rails_5.2.gemfile +7 -0
- data/gemfiles/rails_5.2.gemfile.lock +251 -0
- data/gemfiles/rails_6.0.gemfile +7 -0
- data/gemfiles/rails_6.0.gemfile.lock +267 -0
- data/gemfiles/semantic_logger_3.4.gemfile +7 -0
- data/gemfiles/semantic_logger_3.4.gemfile.lock +265 -0
- data/gemfiles/semantic_logger_4.6.gemfile +7 -0
- data/gemfiles/semantic_logger_4.6.gemfile.lock +265 -0
- data/gemfiles/semantic_logger_4.7.0.gemfile +7 -0
- data/gemfiles/semantic_logger_4.7.0.gemfile.lock +265 -0
- data/gemfiles/semantic_logger_4.7.2.gemfile +7 -0
- data/gemfiles/semantic_logger_4.7.2.gemfile.lock +265 -0
- data/lib/cloudenvoy.rb +96 -2
- data/lib/cloudenvoy/authentication_error.rb +6 -0
- data/lib/cloudenvoy/authenticator.rb +57 -0
- data/lib/cloudenvoy/backend/google_pub_sub.rb +146 -0
- data/lib/cloudenvoy/backend/memory_pub_sub.rb +89 -0
- data/lib/cloudenvoy/config.rb +165 -0
- data/lib/cloudenvoy/engine.rb +20 -0
- data/lib/cloudenvoy/invalid_subscriber_error.rb +6 -0
- data/lib/cloudenvoy/logger_wrapper.rb +167 -0
- data/lib/cloudenvoy/message.rb +96 -0
- data/lib/cloudenvoy/middleware/chain.rb +250 -0
- data/lib/cloudenvoy/pub_sub_client.rb +76 -0
- data/lib/cloudenvoy/publisher.rb +211 -0
- data/lib/cloudenvoy/publisher_logger.rb +32 -0
- data/lib/cloudenvoy/subscriber.rb +222 -0
- data/lib/cloudenvoy/subscriber_logger.rb +26 -0
- data/lib/cloudenvoy/subscription.rb +19 -0
- data/lib/cloudenvoy/testing.rb +106 -0
- data/lib/cloudenvoy/topic.rb +19 -0
- data/lib/cloudenvoy/version.rb +1 -1
- data/lib/tasks/cloudenvoy.rake +61 -0
- metadata +263 -6
data/lib/cloudenvoy.rb
CHANGED
|
@@ -1,8 +1,102 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
|
4
|
+
|
|
3
5
|
require 'cloudenvoy/version'
|
|
6
|
+
require 'cloudenvoy/config'
|
|
7
|
+
|
|
8
|
+
require 'cloudenvoy/authentication_error'
|
|
9
|
+
require 'cloudenvoy/invalid_subscriber_error'
|
|
10
|
+
|
|
11
|
+
require 'cloudenvoy/middleware/chain'
|
|
12
|
+
require 'cloudenvoy/authenticator'
|
|
13
|
+
require 'cloudenvoy/topic'
|
|
14
|
+
require 'cloudenvoy/subscription'
|
|
15
|
+
require 'cloudenvoy/pub_sub_client'
|
|
16
|
+
require 'cloudenvoy/logger_wrapper'
|
|
17
|
+
require 'cloudenvoy/publisher_logger'
|
|
18
|
+
require 'cloudenvoy/subscriber_logger'
|
|
19
|
+
require 'cloudenvoy/message'
|
|
20
|
+
require 'cloudenvoy/publisher'
|
|
21
|
+
require 'cloudenvoy/subscriber'
|
|
4
22
|
|
|
23
|
+
# Define and manage Cloud Pub/Sub publishers and subscribers
|
|
5
24
|
module Cloudenvoy
|
|
6
|
-
|
|
7
|
-
|
|
25
|
+
attr_writer :config
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# Cloudenvoy configurator.
|
|
29
|
+
#
|
|
30
|
+
def self.configure
|
|
31
|
+
yield(config)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#
|
|
35
|
+
# Return the Cloudenvoy configuration.
|
|
36
|
+
#
|
|
37
|
+
# @return [Cloudenvoy::Config] The Cloudenvoy configuration.
|
|
38
|
+
#
|
|
39
|
+
def self.config
|
|
40
|
+
@config ||= Config.new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# Return the Cloudenvoy logger.
|
|
45
|
+
#
|
|
46
|
+
# @return [Logger] The Cloudenvoy logger.
|
|
47
|
+
#
|
|
48
|
+
def self.logger
|
|
49
|
+
config.logger
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#
|
|
53
|
+
# Publish a message to a topic. Shorthand method to Cloudenvoy::PubSubClient#publish.
|
|
54
|
+
#
|
|
55
|
+
# @param [String] topic The name of the topic
|
|
56
|
+
# @param [Hash, String] payload The message content.
|
|
57
|
+
# @param [Hash] attrs The message attributes.
|
|
58
|
+
#
|
|
59
|
+
# @return [Cloudenvoy::Message] The created message.
|
|
60
|
+
#
|
|
61
|
+
def self.publish(topic, payload, attrs = {})
|
|
62
|
+
PubSubClient.publish(topic, payload, attrs)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#
|
|
66
|
+
# Return the list of registered publishers.
|
|
67
|
+
#
|
|
68
|
+
# @return [Set<Cloudenvoy::Subscriber>] The list of registered publishers.
|
|
69
|
+
#
|
|
70
|
+
def self.publishers
|
|
71
|
+
@publishers ||= Set.new
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#
|
|
75
|
+
# Return the list of registered subscribers.
|
|
76
|
+
#
|
|
77
|
+
# @return [Set<Cloudenvoy::Subscriber>] The list of registered subscribers.
|
|
78
|
+
#
|
|
79
|
+
def self.subscribers
|
|
80
|
+
@subscribers ||= Set.new
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# Create/update subscriptions for all registered subscribers.
|
|
85
|
+
#
|
|
86
|
+
# @return [Array<Cloudenvoy::Subscription>] The upserted subscriptions.
|
|
87
|
+
#
|
|
88
|
+
def self.setup_subscribers
|
|
89
|
+
subscribers.flat_map(&:setup)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
#
|
|
93
|
+
# Create/update default topics for all registered publishers.
|
|
94
|
+
#
|
|
95
|
+
# @return [Array<Cloudenvoy::Subscription>] The upserted topics.
|
|
96
|
+
#
|
|
97
|
+
def self.setup_publishers
|
|
98
|
+
publishers.flat_map(&:setup)
|
|
99
|
+
end
|
|
8
100
|
end
|
|
101
|
+
|
|
102
|
+
require 'cloudenvoy/engine' if defined?(::Rails::Engine)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'jwt'
|
|
4
|
+
|
|
5
|
+
module Cloudenvoy
|
|
6
|
+
# Manage token generation and verification
|
|
7
|
+
module Authenticator
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Algorithm used to sign the verification token
|
|
11
|
+
JWT_ALG = 'HS256'
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
# Return the cloudenvoy configuration. See Cloudenvoy#configure.
|
|
15
|
+
#
|
|
16
|
+
# @return [Cloudenvoy::Config] The library configuration.
|
|
17
|
+
#
|
|
18
|
+
def config
|
|
19
|
+
Cloudenvoy.config
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#
|
|
23
|
+
# A Json Web Token (JWT) which is embedded as part of the receiving endpoint
|
|
24
|
+
# and will be used by the processor to authenticate the source of the message.
|
|
25
|
+
#
|
|
26
|
+
# @return [String] The jwt token
|
|
27
|
+
#
|
|
28
|
+
def verification_token
|
|
29
|
+
JWT.encode({ iat: Time.now.to_i }, config.secret, JWT_ALG)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#
|
|
33
|
+
# Verify a bearer token (jwt token)
|
|
34
|
+
#
|
|
35
|
+
# @param [String] bearer_token The token to verify.
|
|
36
|
+
#
|
|
37
|
+
# @return [Boolean] Return true if the token is valid
|
|
38
|
+
#
|
|
39
|
+
def verify(bearer_token)
|
|
40
|
+
JWT.decode(bearer_token, config.secret)
|
|
41
|
+
rescue JWT::VerificationError, JWT::DecodeError
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# Verify a bearer token and raise a `Cloudenvoy::AuthenticationError`
|
|
47
|
+
# if the token is invalid.
|
|
48
|
+
#
|
|
49
|
+
# @param [String] bearer_token The token to verify.
|
|
50
|
+
#
|
|
51
|
+
# @return [Boolean] Return true if the token is valid
|
|
52
|
+
#
|
|
53
|
+
def verify!(bearer_token)
|
|
54
|
+
verify(bearer_token) || raise(AuthenticationError)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'google/cloud/pubsub'
|
|
4
|
+
|
|
5
|
+
module Cloudenvoy
|
|
6
|
+
module Backend
|
|
7
|
+
# Interface to GCP Pub/Sub and Pub/Sub local emulator
|
|
8
|
+
module GooglePubSub
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# Return the cloudenvoy configuration. See Cloudenvoy#configure.
|
|
13
|
+
#
|
|
14
|
+
# @return [Cloudenvoy::Config] The library configuration.
|
|
15
|
+
#
|
|
16
|
+
def config
|
|
17
|
+
Cloudenvoy.config
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Return true if the current config mode is development.
|
|
22
|
+
#
|
|
23
|
+
# @return [Boolean] True if Cloudenvoy is run in development mode.
|
|
24
|
+
#
|
|
25
|
+
def development?
|
|
26
|
+
config.mode == :development
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# Return the backend to use for sending messages.
|
|
31
|
+
#
|
|
32
|
+
# @return [Google::Cloud::Pub] The low level client instance.
|
|
33
|
+
#
|
|
34
|
+
def backend
|
|
35
|
+
@backend ||= Google::Cloud::PubSub.new({
|
|
36
|
+
project_id: config.gcp_project_id,
|
|
37
|
+
emulator_host: development? ? Cloudenvoy::Config::EMULATOR_HOST : nil
|
|
38
|
+
}.compact)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
#
|
|
42
|
+
# Return an authenticated endpoint for processing Pub/Sub webhooks.
|
|
43
|
+
#
|
|
44
|
+
# @return [String] An authenticated endpoint.
|
|
45
|
+
#
|
|
46
|
+
def webhook_url
|
|
47
|
+
"#{config.processor_url}?token=#{Authenticator.verification_token}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
# Publish a message to a topic.
|
|
52
|
+
#
|
|
53
|
+
# @param [String] topic The name of the topic
|
|
54
|
+
# @param [Hash, String] payload The message content.
|
|
55
|
+
# @param [Hash] metadata The message attributes.
|
|
56
|
+
#
|
|
57
|
+
# @return [Cloudenvoy::Message] The created message.
|
|
58
|
+
#
|
|
59
|
+
def publish(topic, payload, metadata = {})
|
|
60
|
+
# Retrieve the topic
|
|
61
|
+
ps_topic = backend.topic(topic, skip_lookup: true)
|
|
62
|
+
|
|
63
|
+
# Publish the message
|
|
64
|
+
ps_msg = ps_topic.publish(payload.to_json, metadata.to_h)
|
|
65
|
+
|
|
66
|
+
# Return formatted message
|
|
67
|
+
Message.new(
|
|
68
|
+
id: ps_msg.message_id,
|
|
69
|
+
payload: payload,
|
|
70
|
+
metadata: metadata,
|
|
71
|
+
topic: topic
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
#
|
|
76
|
+
# Create or update a subscription for a specific topic.
|
|
77
|
+
#
|
|
78
|
+
# @param [String] topic The name of the topic
|
|
79
|
+
# @param [String] name The name of the subscription
|
|
80
|
+
# @param [Hash] opts The subscription configuration options
|
|
81
|
+
# @option opts [Integer] :deadline The maximum number of seconds after a subscriber receives a message
|
|
82
|
+
# before the subscriber should acknowledge the message.
|
|
83
|
+
# @option opts [Boolean] :retain_acked Indicates whether to retain acknowledged messages. If true,
|
|
84
|
+
# then messages are not expunged from the subscription's backlog, even if they are acknowledged,
|
|
85
|
+
# until they fall out of the retention window. Default is false.
|
|
86
|
+
# @option opts [<Type>] :retention How long to retain unacknowledged messages in the subscription's
|
|
87
|
+
# backlog, from the moment a message is published. If retain_acked is true, then this also configures
|
|
88
|
+
# the retention of acknowledged messages, and thus configures how far back in time a Subscription#seek
|
|
89
|
+
# can be done. Cannot be more than 604,800 seconds (7 days) or less than 600 seconds (10 minutes).
|
|
90
|
+
# Default is 604,800 seconds (7 days).
|
|
91
|
+
# @option opts [String] :filter An expression written in the Cloud Pub/Sub filter language.
|
|
92
|
+
# If non-empty, then only Message instances whose attributes field matches the filter are delivered
|
|
93
|
+
# on this subscription. If empty, then no messages are filtered out. Optional.
|
|
94
|
+
#
|
|
95
|
+
# @return [Cloudenvoy::Subscription] The upserted subscription.
|
|
96
|
+
#
|
|
97
|
+
def upsert_subscription(topic, name, opts = {})
|
|
98
|
+
sub_config = opts.to_h.merge(endpoint: webhook_url)
|
|
99
|
+
|
|
100
|
+
# Auto-create topic in development. In non-development environments
|
|
101
|
+
# the create subscription action raises an error if the topic does
|
|
102
|
+
# not exist
|
|
103
|
+
upsert_topic(topic) if development?
|
|
104
|
+
|
|
105
|
+
# Create subscription
|
|
106
|
+
ps_sub =
|
|
107
|
+
begin
|
|
108
|
+
# Retrieve the topic
|
|
109
|
+
ps_topic = backend.topic(topic, skip_lookup: true)
|
|
110
|
+
|
|
111
|
+
# Attempt to create the subscription
|
|
112
|
+
ps_topic.subscribe(name, sub_config)
|
|
113
|
+
rescue Google::Cloud::AlreadyExistsError
|
|
114
|
+
# Update endpoint on subscription
|
|
115
|
+
# Topic is not updated as it is name-dependent
|
|
116
|
+
backend.subscription(name).tap do |e|
|
|
117
|
+
sub_config.each do |k, v|
|
|
118
|
+
e.send("#{k}=", v)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Return formatted subscription
|
|
124
|
+
Subscription.new(name: ps_sub.name, original: ps_sub)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
#
|
|
128
|
+
# Create or update a topic.
|
|
129
|
+
#
|
|
130
|
+
# @param [String] topic The topic name.
|
|
131
|
+
#
|
|
132
|
+
# @return [Cloudenvoy::Topic] The upserted topic.
|
|
133
|
+
#
|
|
134
|
+
def upsert_topic(topic)
|
|
135
|
+
ps_topic = begin
|
|
136
|
+
backend.create_topic(topic)
|
|
137
|
+
rescue Google::Cloud::AlreadyExistsError
|
|
138
|
+
backend.topic(topic)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Return formatted subscription
|
|
142
|
+
Topic.new(name: ps_topic.name, original: ps_topic)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'google/cloud/pubsub'
|
|
4
|
+
|
|
5
|
+
module Cloudenvoy
|
|
6
|
+
module Backend
|
|
7
|
+
# Store messages in a memory queue. Used for testing
|
|
8
|
+
module MemoryPubSub
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# Return the message queue for a specific topic.
|
|
13
|
+
#
|
|
14
|
+
# @param [String] name The topic to retrieve.
|
|
15
|
+
#
|
|
16
|
+
# @return [Array] The list of messages for the provided topic
|
|
17
|
+
#
|
|
18
|
+
def queue(topic)
|
|
19
|
+
@queues ||= {}
|
|
20
|
+
@queues[topic.to_s] ||= []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#
|
|
24
|
+
# Clear all messages in a specific topic.
|
|
25
|
+
#
|
|
26
|
+
# @param [String] name The topic to clear.
|
|
27
|
+
#
|
|
28
|
+
# @return [Array] The cleared array.
|
|
29
|
+
#
|
|
30
|
+
def clear(topic)
|
|
31
|
+
queue(topic).clear
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#
|
|
35
|
+
# Clear all messages across all topics.
|
|
36
|
+
#
|
|
37
|
+
# @param [String] name The topic to clear.
|
|
38
|
+
#
|
|
39
|
+
def clear_all
|
|
40
|
+
@queues&.values&.each { |e| e.clear }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# Publish a message to a topic.
|
|
45
|
+
#
|
|
46
|
+
# @param [String] topic The name of the topic
|
|
47
|
+
# @param [Hash, String] payload The message content.
|
|
48
|
+
# @param [Hash] attrs The message attributes.
|
|
49
|
+
#
|
|
50
|
+
# @return [Cloudenvoy::Message] The created message.
|
|
51
|
+
#
|
|
52
|
+
def publish(topic, payload, metadata = {})
|
|
53
|
+
msg = Message.new(
|
|
54
|
+
id: SecureRandom.uuid,
|
|
55
|
+
payload: payload,
|
|
56
|
+
metadata: metadata,
|
|
57
|
+
topic: topic
|
|
58
|
+
)
|
|
59
|
+
queue(topic).push(msg)
|
|
60
|
+
|
|
61
|
+
msg
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
# Create or update a subscription for a specific topic.
|
|
66
|
+
#
|
|
67
|
+
# @param [String] topic The name of the topic
|
|
68
|
+
# @param [String] name The name of the subscription
|
|
69
|
+
# @param [Hash] opts The subscription configuration options
|
|
70
|
+
#
|
|
71
|
+
# @return [Cloudenvoy::Subscription] The upserted subscription.
|
|
72
|
+
#
|
|
73
|
+
def upsert_subscription(_topic, name, _opts)
|
|
74
|
+
Subscription.new(name: name)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
#
|
|
78
|
+
# Create or update a topic.
|
|
79
|
+
#
|
|
80
|
+
# @param [String] topic The topic name.
|
|
81
|
+
#
|
|
82
|
+
# @return [Cloudenvoy::Topic] The upserted topic.
|
|
83
|
+
#
|
|
84
|
+
def upsert_topic(topic)
|
|
85
|
+
Topic.new(name: topic)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
|
|
5
|
+
module Cloudenvoy
|
|
6
|
+
# Holds cloudenvoy configuration. See Cloudenvoy#configure
|
|
7
|
+
class Config
|
|
8
|
+
attr_writer :secret, :gcp_project_id,
|
|
9
|
+
:gcp_sub_prefix, :processor_path, :logger, :mode
|
|
10
|
+
|
|
11
|
+
# Emulator host
|
|
12
|
+
EMULATOR_HOST = ENV['PUBSUB_EMULATOR_HOST'] || 'localhost:8085'
|
|
13
|
+
|
|
14
|
+
# Default application path used for processing messages
|
|
15
|
+
DEFAULT_PROCESSOR_PATH = '/cloudenvoy/receive'
|
|
16
|
+
|
|
17
|
+
PROCESSOR_HOST_MISSING = <<~DOC
|
|
18
|
+
Missing host for processing.
|
|
19
|
+
Please specify a processor hostname in form of `https://some-public-dns.example.com`'
|
|
20
|
+
DOC
|
|
21
|
+
SUB_PREFIX_MISSING_ERROR = <<~DOC
|
|
22
|
+
Missing GCP subscription prefix.
|
|
23
|
+
Please specify a subscription prefix in the form of `my-app`.
|
|
24
|
+
DOC
|
|
25
|
+
PROJECT_ID_MISSING_ERROR = <<~DOC
|
|
26
|
+
Missing GCP project ID.
|
|
27
|
+
Please specify a project ID in the cloudenvoy configurator.
|
|
28
|
+
DOC
|
|
29
|
+
SECRET_MISSING_ERROR = <<~DOC
|
|
30
|
+
Missing cloudenvoy secret.
|
|
31
|
+
Please specify a secret in the cloudenvoy initializer or add Rails secret_key_base in your credentials
|
|
32
|
+
DOC
|
|
33
|
+
|
|
34
|
+
#
|
|
35
|
+
# The operating mode.
|
|
36
|
+
# - :production => send messages to GCP Pub/Sub
|
|
37
|
+
# - :development => send message to gcloud CLI Pub/Sub emulator
|
|
38
|
+
#
|
|
39
|
+
# @return [<Type>] <description>
|
|
40
|
+
#
|
|
41
|
+
def mode
|
|
42
|
+
@mode ||= environment == 'development' ? :development : :production
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# Return the current environment.
|
|
47
|
+
#
|
|
48
|
+
# @return [String] The environment name.
|
|
49
|
+
#
|
|
50
|
+
def environment
|
|
51
|
+
ENV['CLOUDENVOY_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#
|
|
55
|
+
# Return the Cloudenvoy logger.
|
|
56
|
+
#
|
|
57
|
+
# @return [Logger, any] The cloudenvoy logger.
|
|
58
|
+
#
|
|
59
|
+
def logger
|
|
60
|
+
@logger ||= defined?(Rails) ? Rails.logger : ::Logger.new(STDOUT)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
#
|
|
64
|
+
# Return the full URL of the processor. Message payloads will be sent
|
|
65
|
+
# to this URL.
|
|
66
|
+
#
|
|
67
|
+
# @return [String] The processor URL.
|
|
68
|
+
#
|
|
69
|
+
def processor_url
|
|
70
|
+
File.join(processor_host, processor_path)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
#
|
|
74
|
+
# Set the processor host. In the context of Rails the host will
|
|
75
|
+
# also be added to the list of authorized Rails hosts.
|
|
76
|
+
#
|
|
77
|
+
# @param [String] val The processor host to set.
|
|
78
|
+
#
|
|
79
|
+
def processor_host=(val)
|
|
80
|
+
@processor_host = val
|
|
81
|
+
|
|
82
|
+
# Check if Rails supports host filtering
|
|
83
|
+
return unless val &&
|
|
84
|
+
defined?(Rails) &&
|
|
85
|
+
Rails.application.config.respond_to?(:hosts) &&
|
|
86
|
+
Rails.application.config.hosts&.any?
|
|
87
|
+
|
|
88
|
+
# Add processor host to the list of authorized hosts
|
|
89
|
+
Rails.application.config.hosts << val.gsub(%r{https?://}, '')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
#
|
|
93
|
+
# The hostname of the application processing the messages. The hostname must
|
|
94
|
+
# be reachable from Cloud Pub/Sub.
|
|
95
|
+
#
|
|
96
|
+
# @return [String] The processor host.
|
|
97
|
+
#
|
|
98
|
+
def processor_host
|
|
99
|
+
@processor_host || raise(StandardError, PROCESSOR_HOST_MISSING)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
#
|
|
103
|
+
# The path on the host when message payloads will be sent.
|
|
104
|
+
# Default to `/cloudenvoy/receive`
|
|
105
|
+
#
|
|
106
|
+
#
|
|
107
|
+
# @return [String] The processor path
|
|
108
|
+
#
|
|
109
|
+
def processor_path
|
|
110
|
+
@processor_path || DEFAULT_PROCESSOR_PATH
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
#
|
|
114
|
+
# Return the prefix used for queues.
|
|
115
|
+
#
|
|
116
|
+
# @return [String] The prefix used when creating subscriptions.
|
|
117
|
+
#
|
|
118
|
+
def gcp_sub_prefix
|
|
119
|
+
@gcp_sub_prefix || raise(StandardError, SUB_PREFIX_MISSING_ERROR)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
#
|
|
123
|
+
# Return the GCP project ID.
|
|
124
|
+
#
|
|
125
|
+
# @return [String] The ID of the project where pub/sub messages are hosted.
|
|
126
|
+
#
|
|
127
|
+
def gcp_project_id
|
|
128
|
+
@gcp_project_id || raise(StandardError, PROJECT_ID_MISSING_ERROR)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
#
|
|
132
|
+
# Return the secret to use to sign the verification tokens
|
|
133
|
+
# attached to messages.
|
|
134
|
+
#
|
|
135
|
+
# @return [String] The cloudenvoy secret
|
|
136
|
+
#
|
|
137
|
+
def secret
|
|
138
|
+
@secret || (
|
|
139
|
+
defined?(Rails) && Rails.application.credentials&.dig(:secret_key_base)
|
|
140
|
+
) || raise(StandardError, SECRET_MISSING_ERROR)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
#
|
|
144
|
+
# Return the chain of publisher middlewares.
|
|
145
|
+
#
|
|
146
|
+
# @return [Cloudenvoy::Middleware::Chain] The chain of middlewares.
|
|
147
|
+
#
|
|
148
|
+
def publisher_middleware
|
|
149
|
+
@publisher_middleware ||= Middleware::Chain.new
|
|
150
|
+
yield @publisher_middleware if block_given?
|
|
151
|
+
@publisher_middleware
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
#
|
|
155
|
+
# Return the chain of subscriber middlewares.
|
|
156
|
+
#
|
|
157
|
+
# @return [Cloudenvoy::Middleware::Chain] The chain of middlewares.
|
|
158
|
+
#
|
|
159
|
+
def subscriber_middleware
|
|
160
|
+
@subscriber_middleware ||= Middleware::Chain.new
|
|
161
|
+
yield @subscriber_middleware if block_given?
|
|
162
|
+
@subscriber_middleware
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|