cloudenvoy 0.1.0.dev → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|