workable-pact-provider-verifier 1.24.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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