workable-pact-provider-verifier 1.24.2

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.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pact/provider_verifier/cli/verify'
3
+ Pact::ProviderVerifier::CLI::Verify.start
@@ -0,0 +1,2 @@
1
+ require 'pact/provider_verifier/version'
2
+ require 'pact/provider_verifier/app'
@@ -0,0 +1,34 @@
1
+ module Pact
2
+ module ProviderVerifier
3
+ class AddHeaderMiddlware
4
+
5
+ def initialize app, headers
6
+ @app = app
7
+ @headers = headers
8
+ end
9
+
10
+ def call env
11
+ @app.call(add_headers(env))
12
+ end
13
+
14
+ def add_headers env
15
+ new_env = env.dup
16
+ @headers.each_pair do | key, value |
17
+ header_name = "HTTP_#{key.upcase.gsub("-", "_")}"
18
+ warn_about_header_replacement key, new_env[header_name], value
19
+ new_env[header_name] = value
20
+ end
21
+ new_env
22
+ end
23
+
24
+ def warn_about_header_replacement header_name, existing_value, new_value
25
+ if existing_value.nil?
26
+ $stderr.puts "WARN: Adding header '#{header_name}: #{new_value}' to replayed request. This header did not exist in the pact, and hence this test cannot confirm that the request will work in real life."
27
+ else
28
+ # Don't mess up the json formatter by using stdout here
29
+ $stderr.puts "INFO: Replacing header '#{header_name}: #{existing_value}' with '#{header_name}: #{new_value}'"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,51 @@
1
+ require 'pact/pact_broker'
2
+ require 'ostruct'
3
+ require 'pact/provider/pact_uri'
4
+
5
+ module Pact
6
+ module ProviderVerifier
7
+ class AggregatePactConfigs
8
+
9
+ def self.call(pact_urls, provider_name, consumer_version_tags, provider_version_tags, pact_broker_base_url, http_client_options)
10
+ new(pact_urls, provider_name, consumer_version_tags, provider_version_tags, pact_broker_base_url, http_client_options).call
11
+ end
12
+
13
+ def initialize(pact_urls, provider_name, consumer_version_tags, provider_version_tags, pact_broker_base_url, http_client_options)
14
+ @pact_urls = pact_urls
15
+ @provider_name = provider_name
16
+ @consumer_version_tags = consumer_version_tags
17
+ @provider_version_tags = provider_version_tags
18
+ @pact_broker_base_url = pact_broker_base_url
19
+ @http_client_options = http_client_options
20
+ end
21
+
22
+ def call
23
+ pacts_urls_from_broker + specified_pact_uris
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :pact_urls, :provider_name, :consumer_version_tags, :provider_version_tags, :pact_broker_base_url, :http_client_options
29
+
30
+ def specified_pact_uris
31
+ pact_urls.collect{ | url | Pact::PactBroker.build_pact_uri(url) }
32
+ end
33
+
34
+ def pacts_urls_from_broker
35
+ if pact_broker_base_url && provider_name
36
+ pacts_for_verification
37
+ else
38
+ []
39
+ end
40
+ end
41
+
42
+ def pacts_for_verification
43
+ @pacts_for_verification ||= Pact::PactBroker.fetch_pact_uris_for_verification(provider_name, consumer_version_selectors, provider_version_tags, pact_broker_base_url, http_client_options)
44
+ end
45
+
46
+ def consumer_version_selectors
47
+ consumer_version_tags.collect{ |tag| { tag: tag, latest: true } }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,229 @@
1
+ require 'pact/wait_until_server_available'
2
+ require 'pact/provider_verifier/add_header_middlware'
3
+ require 'pact/provider_verifier/provider_states/add_provider_states_header'
4
+ require 'pact/provider_verifier/provider_states/remove_provider_states_header_middleware'
5
+ require 'pact/provider_verifier/custom_middleware'
6
+ require 'pact/provider/rspec'
7
+ require 'pact/message'
8
+ require 'pact/cli/run_pact_verification'
9
+ require 'pact/provider_verifier/aggregate_pact_configs'
10
+ require 'rack/reverse_proxy'
11
+ require 'faraday_middleware'
12
+ require 'json'
13
+
14
+ module Pact
15
+ module ProviderVerifier
16
+ class App
17
+ include Pact::WaitUntilServerAvailable
18
+
19
+ PROXY_PACT_HELPER = File.expand_path(File.join(File.dirname(__FILE__), "pact_helper.rb"))
20
+ attr_reader :pact_urls, :options, :consumer_version_tags, :provider_version_tags
21
+
22
+ def initialize pact_urls, options = {}
23
+ @pact_urls = pact_urls
24
+ @options = options
25
+ @consumer_version_tags = options[:consumer_version_tag] || []
26
+ @provider_version_tags = options[:provider_version_tag] || []
27
+ end
28
+
29
+ def self.call pact_urls, options
30
+ new(pact_urls, options).call
31
+ end
32
+
33
+ def call
34
+ setup
35
+
36
+ pact_urls = all_pact_urls
37
+ wait_until_provider_available
38
+ exit_statuses = pact_urls.collect do |pact_uri|
39
+ verify_pact pact_uri
40
+ end
41
+
42
+ exit_statuses.all?{ | status | status == 0 }
43
+ end
44
+
45
+ private
46
+
47
+
48
+ def setup
49
+ print_deprecation_note
50
+ set_environment_variables
51
+ require_rspec_monkeypatch_for_jsonl
52
+ require_pact_project_pact_helper # Beth: not sure if this is needed, hangover from pact-provider-proxy?
53
+ set_broker_token_env_var
54
+ end
55
+
56
+ def set_environment_variables
57
+ ENV['PROVIDER_STATES_SETUP_URL'] = options.provider_states_setup_url
58
+ ENV['VERBOSE_LOGGING'] = options.verbose if options.verbose
59
+ ENV['CUSTOM_PROVIDER_HEADER'] = custom_provider_headers_for_env_var if custom_provider_headers_for_env_var
60
+ ENV['MONKEYPATCH'] = options.monkeypatch.join("\n") if options.monkeypatch && options.monkeypatch.any?
61
+ end
62
+
63
+ def configure_service_provider
64
+ # Have to declare these locally as the class scope gets lost within the block
65
+ application = configure_reverse_proxy
66
+ application = configure_provider_states_header_removal_middleware(application)
67
+ application = configure_custom_middleware(application)
68
+ application = configure_custom_header_middleware(application)
69
+
70
+ this = self
71
+
72
+ Pact.service_provider "Running Provider Application" do
73
+ app do
74
+ application
75
+ end
76
+
77
+ if this.options.provider_app_version
78
+ app_version this.options.provider_app_version
79
+ end
80
+
81
+ if this.provider_version_tags.any?
82
+ app_version_tags this.provider_version_tags
83
+ end
84
+
85
+ publish_verification_results this.options.publish_verification_results
86
+ end
87
+ end
88
+
89
+ def configure_reverse_proxy
90
+ provider_base_url = options.provider_base_url
91
+ Rack::ReverseProxy.new do
92
+ reverse_proxy_options(
93
+ verify_mode: OpenSSL::SSL::VERIFY_NONE,
94
+ preserve_host: true,
95
+ x_forwarded_headers: false
96
+ )
97
+ reverse_proxy %r{(.*)}, "#{provider_base_url}$1"
98
+ end
99
+ end
100
+
101
+ def configure_custom_header_middleware rack_reverse_proxy
102
+ if options.custom_provider_header
103
+ Pact::ProviderVerifier::AddHeaderMiddlware.new(rack_reverse_proxy, parse_header)
104
+ else
105
+ rack_reverse_proxy
106
+ end
107
+ end
108
+
109
+ def configure_custom_middleware app
110
+ if options.custom_middleware && options.custom_middleware.any?
111
+ require_custom_middlware
112
+ apply_custom_middleware(app)
113
+ else
114
+ app
115
+ end
116
+ end
117
+
118
+ def configure_provider_states_header_removal_middleware app
119
+ ProviderStates::RemoveProviderStatesHeaderMiddleware.new(app)
120
+ end
121
+
122
+ def require_custom_middlware
123
+ options.custom_middleware.each do |file|
124
+ $stdout.puts "DEBUG: Requiring custom middleware file #{file}" if options.verbose
125
+ begin
126
+ require file
127
+ rescue LoadError => e
128
+ $stderr.puts "ERROR: #{e.class} - #{e.message}. Please specify an absolute path."
129
+ exit(1)
130
+ end
131
+ end
132
+ end
133
+
134
+ def apply_custom_middleware app
135
+ CustomMiddleware.descendants.inject(app) do | app, clazz |
136
+ Pact.configuration.output_stream.puts "INFO: Adding custom middleware #{clazz}"
137
+ clazz.new(app)
138
+ end
139
+ end
140
+
141
+ def verify_pact(pact_uri)
142
+ begin
143
+ verify_options = {
144
+ pact_helper: PROXY_PACT_HELPER,
145
+ pact_uri: pact_uri,
146
+ backtrace: ENV['BACKTRACE'] == 'true',
147
+ pact_broker_username: options.broker_username,
148
+ pact_broker_password: options.broker_password,
149
+ pact_broker_token: options.broker_token,
150
+ format: options.format,
151
+ out: options.out,
152
+ request_customizer: ProviderStates::AddProviderStatesHeader
153
+ }
154
+ verify_options[:description] = ENV['PACT_DESCRIPTION'] if ENV['PACT_DESCRIPTION']
155
+ verify_options[:provider_state] = ENV['PACT_PROVIDER_STATE'] if ENV['PACT_PROVIDER_STATE']
156
+
157
+ reset_pact_configuration
158
+ # Really, this should call the PactSpecRunner directly, rather than using the CLI class.
159
+ Cli::RunPactVerification.call(verify_options)
160
+ rescue SystemExit => e
161
+ e.status
162
+ end
163
+ end
164
+
165
+ def reset_pact_configuration
166
+ require 'pact/configuration'
167
+ require 'pact/consumer/world'
168
+ require 'pact/provider/world'
169
+ Pact.clear_configuration
170
+ Pact.clear_consumer_world
171
+ Pact.clear_provider_world
172
+ configure_service_provider
173
+ end
174
+
175
+ def all_pact_urls
176
+ http_client_options = { username: options.broker_username, password: options.broker_password, token: options.broker_token, verbose: options.verbose }
177
+ AggregatePactConfigs.call(pact_urls, options.provider, consumer_version_tags, provider_version_tags, options.pact_broker_base_url, http_client_options)
178
+ end
179
+
180
+ def require_pact_project_pact_helper
181
+ require ENV['PACT_PROJECT_PACT_HELPER'] if ENV.fetch('PACT_PROJECT_PACT_HELPER','') != ''
182
+ end
183
+
184
+ def require_rspec_monkeypatch_for_jsonl
185
+ if options.format == 'json'
186
+ require 'pact/provider_verifier/rspec_json_formatter_monkeypatch'
187
+ end
188
+ end
189
+
190
+ def custom_provider_headers_for_env_var
191
+ if options.custom_provider_header && options.custom_provider_header.any?
192
+ options.custom_provider_header.join("\n")
193
+ end
194
+ end
195
+
196
+ def parse_header
197
+ options.custom_provider_header.each_with_object({}) do | custom_provider_header, header_hash |
198
+ header_name, header_value = custom_provider_header.split(":", 2).collect(&:strip)
199
+ header_hash[header_name] = header_value
200
+ end
201
+ end
202
+
203
+ def print_deprecation_note
204
+ if options.provider_states_url
205
+ $stderr.puts "WARN: The --provider-states-url option is deprecated and the URL endpoint can be removed from the application"
206
+ end
207
+ end
208
+
209
+ def wait_until_provider_available
210
+ if options.wait && options.wait != 0
211
+ uri = URI(options.provider_base_url)
212
+ $stderr.puts "INFO: Polling for up to #{options.wait} seconds for provider to become available at #{uri.host}:#{uri.port}..."
213
+ up = wait_until_server_available(uri.host, uri.port, options.wait)
214
+ if up
215
+ $stderr.puts "INFO: Provider available, proceeding with verifications"
216
+ else
217
+ $stderr.puts "WARN: Provider does not appear to be up on #{uri.host}:#{uri.port}... proceeding with verifications anyway"
218
+ end
219
+ end
220
+ end
221
+
222
+ def set_broker_token_env_var
223
+ if options.broker_token && !ENV['PACT_BROKER_TOKEN']
224
+ ENV['PACT_BROKER_TOKEN'] = options.broker_token
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,71 @@
1
+ require 'thor'
2
+
3
+ module Pact
4
+ module ProviderVerifier
5
+ module CLI
6
+ ##
7
+ # Custom Thor task allows the following:
8
+ #
9
+ # `script arg1 arg2` to be interpreted as `script <default_task> arg1 arg2`
10
+ # `--option 1 --option 2` to be interpreted as `--option 1 2` (the standard Thor format for multiple value options)
11
+ # `script --help` to display the help for the default task instead of the command list
12
+ #
13
+ class CustomThor < ::Thor
14
+
15
+ no_commands do
16
+ def self.start given_args = ARGV, config = {}
17
+ super(massage_args(given_args))
18
+ end
19
+
20
+ def help *args
21
+ if args.empty?
22
+ super(self.class.default_task)
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ def self.massage_args argv
29
+ prepend_default_task_name(turn_muliple_tag_options_into_array(argv))
30
+ end
31
+
32
+ def self.prepend_default_task_name argv
33
+ if known_first_arguments.include?(argv[0])
34
+ argv
35
+ else
36
+ [default_command] + argv
37
+ end
38
+ end
39
+
40
+ # other task names, help, and the help shortcuts
41
+ def self.known_first_arguments
42
+ @known_first_arguments ||= tasks.keys + ::Thor::HELP_MAPPINGS + ['help']
43
+ end
44
+
45
+ def self.turn_muliple_tag_options_into_array argv
46
+ new_argv = []
47
+ opt_name = nil
48
+ argv.each_with_index do | arg, i |
49
+ if arg.start_with?('-')
50
+ opt_name = arg
51
+ existing = new_argv.find { | a | a.first == opt_name }
52
+ if !existing
53
+ new_argv << [arg]
54
+ end
55
+ else
56
+ if opt_name
57
+ existing = new_argv.find { | a | a.first == opt_name }
58
+ existing << arg
59
+ opt_name = nil
60
+ else
61
+ new_argv << [arg]
62
+ end
63
+ end
64
+ end
65
+ new_argv.flatten
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,80 @@
1
+ require 'thor'
2
+ require 'socket'
3
+ require 'pact/provider_verifier/app'
4
+ require 'pact/provider_verifier/cli/custom_thor'
5
+
6
+ module Pact
7
+ module ProviderVerifier
8
+ module CLI
9
+ class Verify < CustomThor
10
+
11
+ class InvalidArgumentsError < ::Thor::Error; end
12
+
13
+ desc 'PACT_URL ...', "Verify pact(s) against a provider. Supports local and networked (http-based) files."
14
+ method_option :provider_base_url, aliases: "-h", desc: "Provider host URL", :required => true
15
+ method_option :provider_states_setup_url, aliases: "-c", desc: "Base URL to setup the provider states at", :required => false
16
+ method_option :pact_broker_base_url, desc: "Base URL of the Pact Broker from which to retrieve the pacts.", :required => false
17
+ method_option :broker_username, aliases: "-n", desc: "Pact Broker basic auth username", :required => false
18
+ method_option :broker_password, aliases: "-p", desc: "Pact Broker basic auth password", :required => false
19
+ method_option :broker_token, aliases: "-k", desc: "Pact Broker bearer token", :required => false
20
+ method_option :provider, required: false
21
+ method_option :consumer_version_tag, type: :array, banner: "TAG", desc: "Retrieve the latest pacts with this consumer version tag. Used in conjunction with --provider. May be specified multiple times.", :required => false
22
+ method_option :provider_version_tag, type: :array, banner: "TAG", desc: "Tag to apply to the provider application version. May be specified multiple times.", :required => false
23
+ method_option :provider_app_version, aliases: "-a", desc: "Provider application version, required when publishing verification results", :required => false
24
+ method_option :publish_verification_results, aliases: "-r", desc: "Publish verification results to the broker", required: false, type: :boolean, default: false
25
+ method_option :custom_provider_header, type: :array, banner: 'CUSTOM_PROVIDER_HEADER', desc: "Header to add to provider state set up and pact verification requests. eg 'Authorization: Basic cGFjdDpwYWN0'. May be specified multiple times.", :required => false
26
+ method_option :custom_middleware, type: :array, banner: 'FILE', desc: "Ruby file containing a class implementing Pact::ProviderVerifier::CustomMiddleware. This allows the response to be modified before replaying. Use with caution!", :required => false
27
+ method_option :monkeypatch, hide: true, type: :array, :required => false
28
+ method_option :verbose, aliases: "-v", desc: "Verbose output", :required => false
29
+ method_option :provider_states_url, aliases: "-s", :required => false, hide: true
30
+ method_option :format, banner: "FORMATTER", aliases: "-f", desc: "RSpec formatter. Defaults to custom Pact formatter. Other options are json and RspecJunitFormatter (which outputs xml)."
31
+ method_option :out, aliases: "-o", banner: "FILE", desc: "Write output to a file instead of $stdout."
32
+ method_option :ignore_failures, type: :boolean, default: false, desc: "If specified, process will always exit with exit code 0", hide: true
33
+ method_option :pact_urls, aliases: "-u", hide: true, :required => false
34
+ method_option :wait, banner: "SECONDS", required: false, type: :numeric, desc: "The number of seconds to poll for the provider to become available before running the verification", default: 0
35
+
36
+ def verify(*pact_urls)
37
+ validate_verify
38
+ print_deprecation_warnings
39
+ success = Pact::ProviderVerifier::App.call(merged_urls(pact_urls), options)
40
+ exit_with_non_zero_status if !success && !options.ignore_failures
41
+ end
42
+
43
+ default_task :verify
44
+
45
+ desc 'version', 'Show the pact-provider-verifier gem version'
46
+ def version
47
+ require 'pact/provider_verifier/version'
48
+ puts Pact::ProviderVerifier::VERSION
49
+ end
50
+
51
+ no_commands do
52
+ def merged_urls pact_urls_from_args
53
+ from_opts = options.pact_urls ? options.pact_urls.split(',') : []
54
+ from_opts + pact_urls_from_args
55
+ end
56
+
57
+ def print_deprecation_warnings
58
+ if options.pact_urls
59
+ $stderr.puts "WARN: The --pact-urls option is deprecated. Please pass in a space separated list of URLs as the first arguments to the pact-provider-verifier command."
60
+ end
61
+ end
62
+
63
+ def validate_verify
64
+ if options.pact_broker_base_url && (options.provider.nil? || options.provider == "")
65
+ raise InvalidArgumentsError, "No value provided for required option '--provider'"
66
+ end
67
+ end
68
+
69
+ def exit_with_non_zero_status
70
+ exit 1
71
+ end
72
+
73
+ def exit_on_failure?
74
+ true
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end