pacto-server 0.4.0.rc1 → 0.4.0.rc2
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/bin/pacto-server +2 -7
- data/lib/pacto/server/cli.rb +66 -0
- data/lib/pacto/server/config.rb +1 -50
- data/lib/pacto/server/proxy.rb +49 -0
- data/lib/pacto/server/settings.rb +67 -1
- metadata +9 -36
- data/lib/pacto/server/api.rb +0 -102
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48d92c082f19c254b64fa04ff72916ca26006864
|
4
|
+
data.tar.gz: dfdbd70436fe3d864b3a6743fdf150be10964e75
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 633eeeacc1f2b780fe66504de21391427baea0470d67615b19299d1291aee6483158bd515f0bbbf83eb4b23ec8ac19f3fbbedce84ffb463a8ca4465acd4fcf94
|
7
|
+
data.tar.gz: 596c074835077add5d5cbc4988730e7e821fe6bcce5b09a0180cc265ebba59b95216fb49456ed336a4c9f303666512c00f19181c7d0b95facf4f9783d2800cae
|
data/bin/pacto-server
CHANGED
@@ -1,9 +1,4 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require '
|
3
|
-
require 'goliath/runner'
|
4
|
-
require 'pacto/server'
|
2
|
+
require 'pacto/server/cli'
|
5
3
|
|
6
|
-
|
7
|
-
runner.log_file = File.expand_path(runner.log_file, Dir.pwd) if runner.log_file
|
8
|
-
runner.app = Goliath::Rack::Builder.build(Pacto::Server::API, runner.api)
|
9
|
-
runner.run
|
4
|
+
Pacto::Server::CLI.start
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'pacto/server'
|
3
|
+
|
4
|
+
module Pacto
|
5
|
+
module Server
|
6
|
+
class CLI < Thor
|
7
|
+
class << self
|
8
|
+
DEFAULTS = {
|
9
|
+
stdout: true,
|
10
|
+
log_file: 'pacto.log',
|
11
|
+
# :config => 'pacto/config/pacto_server.rb',
|
12
|
+
strict: false,
|
13
|
+
stub: true,
|
14
|
+
live: false,
|
15
|
+
generate: false,
|
16
|
+
verbose: true,
|
17
|
+
validate: true,
|
18
|
+
directory: File.join(Dir.pwd, 'contracts'),
|
19
|
+
port: 9000,
|
20
|
+
format: :legacy,
|
21
|
+
stenographer_log_file: File.expand_path('pacto_stenographer.log', Dir.pwd),
|
22
|
+
strip_port: true
|
23
|
+
}
|
24
|
+
|
25
|
+
def server_options
|
26
|
+
method_option :port, default: 4567, desc: 'The port to run the server on'
|
27
|
+
method_option :directory, default: DEFAULTS[:directory], desc: 'The directory containing contracts'
|
28
|
+
method_option :strict, default: DEFAULTS[:strict], desc: 'Whether Pacto should use strict matching or not'
|
29
|
+
method_option :format, default: DEFAULTS[:format], desc: 'The contract format to use'
|
30
|
+
method_option :strip_port, default: DEFAULTS[:strip_port], desc: 'If pacto should remove the port from URLs before forwarding'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'stub [CONTRACTS...]', 'Launches a stub server for a set of contracts'
|
35
|
+
method_option :port, type: :numeric, desc: 'The port to listen on', default: 3000
|
36
|
+
method_option :spy, type: :boolean, desc: 'Display traffic received by Pacto'
|
37
|
+
server_options
|
38
|
+
def stub(*_contracts)
|
39
|
+
setup_interrupt
|
40
|
+
server_options = @options.dup
|
41
|
+
server_options[:stub] = true
|
42
|
+
Pacto::Server::HTTP.run('0.0.0.0', options.port, server_options)
|
43
|
+
end
|
44
|
+
|
45
|
+
desc 'proxy [CONTRACTS...]', 'Launches an intercepting proxy server for a set of contracts'
|
46
|
+
method_option :to, type: :string, desc: 'The target host for forwarded requests'
|
47
|
+
method_option :port, type: :numeric, desc: 'The port to listen on', default: 3000
|
48
|
+
method_option :spy, type: :boolean, desc: 'Display traffic received by Pacto'
|
49
|
+
def proxy(*_contracts)
|
50
|
+
setup_interrupt
|
51
|
+
server_options = @options.dup
|
52
|
+
server_options[:live] = true
|
53
|
+
Pacto::Server::HTTP.run('0.0.0.0', options.port, server_options)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def setup_interrupt
|
59
|
+
trap('INT') do
|
60
|
+
say 'Exiting...'
|
61
|
+
exit
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/pacto/server/config.rb
CHANGED
@@ -1,51 +1,2 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
|
-
|
3
|
-
if File.readable? '.tokens.json'
|
4
|
-
MultiJson.load(File.read '.tokens.json')
|
5
|
-
else
|
6
|
-
{}
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
def prepare_contracts(contracts)
|
11
|
-
contracts.stub_providers if options[:stub]
|
12
|
-
end
|
13
|
-
|
14
|
-
config[:backend_host] = options[:backend_host]
|
15
|
-
config[:strip_port] = options[:strip_port]
|
16
|
-
config[:strip_dev] = options[:strip_dev]
|
17
|
-
config[:port] = port
|
18
|
-
contracts_path = options[:directory] || File.expand_path('contracts', Dir.pwd)
|
19
|
-
Pacto.configure do |pacto_config|
|
20
|
-
pacto_config.logger = options[:pacto_logger] || logger
|
21
|
-
pacto_config.loggerl.log_level = config[:pacto_log_level] if config[:pacto_log_level]
|
22
|
-
pacto_config.contracts_path = contracts_path
|
23
|
-
pacto_config.strict_matchers = options[:strict]
|
24
|
-
pacto_config.generator_options = {
|
25
|
-
schema_version: :draft3,
|
26
|
-
token_map: token_map
|
27
|
-
}
|
28
|
-
pacto_config.stenographer_log_file = options[:stenographer_log_file]
|
29
|
-
end
|
30
|
-
|
31
|
-
if options[:generate]
|
32
|
-
Pacto.generate!
|
33
|
-
logger.info 'Pacto generation mode enabled'
|
34
|
-
end
|
35
|
-
|
36
|
-
if options[:recursive_loading]
|
37
|
-
Dir["#{contracts_path}/*"].each do |host_dir|
|
38
|
-
host = File.basename host_dir
|
39
|
-
prepare_contracts Pacto.load_contracts(host_dir, "https://#{host}", options[:format])
|
40
|
-
end
|
41
|
-
else
|
42
|
-
host_pattern = options[:backend_host] || 'https://{server}'
|
43
|
-
prepare_contracts Pacto.load_contracts(contracts_path, host_pattern, options[:format])
|
44
|
-
end
|
45
|
-
|
46
|
-
Pacto.validate! if options[:validate]
|
47
|
-
|
48
|
-
if options[:live]
|
49
|
-
# WebMock.reset!
|
50
|
-
WebMock.allow_net_connect!
|
51
|
-
end
|
2
|
+
Pacto::Server::Settings::OptionHandler.new(port, logger, config).handle(options)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Pacto
|
2
|
+
module Server
|
3
|
+
module Proxy
|
4
|
+
def proxy_request(pacto_request)
|
5
|
+
prepare_to_forward(pacto_request)
|
6
|
+
pacto_response = forward(pacto_request)
|
7
|
+
prepare_to_respond(pacto_response)
|
8
|
+
pacto_response.body = rewrite(pacto_response.body)
|
9
|
+
pacto_response
|
10
|
+
end
|
11
|
+
|
12
|
+
def prepare_to_forward(pacto_request)
|
13
|
+
host = host_for(pacto_request)
|
14
|
+
fail 'Could not determine request host' if host.nil?
|
15
|
+
host.gsub!('.dev', '.com') if settings[:strip_dev]
|
16
|
+
scheme, host = host.split('://')
|
17
|
+
host, scheme = scheme, host if host.nil?
|
18
|
+
host, _port = host.split(':')
|
19
|
+
scheme ||= 'https'
|
20
|
+
pacto_request.uri = Addressable::URI.heuristic_parse("#{scheme}://#{host}#{pacto_request.uri}")
|
21
|
+
# FIXME: We're stripping accept-encoding and transfer-encoding rather than dealing with the encodings
|
22
|
+
pacto_request.headers.delete_if { |k, _v| %w(host content-length accept-encoding transfer-encoding).include? k.downcase }
|
23
|
+
end
|
24
|
+
|
25
|
+
def rewrite(body)
|
26
|
+
return body unless settings[:strip_dev]
|
27
|
+
# FIXME: This is pretty hacky and needs to be rethought, but here to support hypermedia APIs
|
28
|
+
# This rewrites the response body so that URLs that may link to other services are rewritten
|
29
|
+
# to also passs through the Pacto server.
|
30
|
+
body.gsub('.com', ".dev:#{settings[:port]}").gsub(/https\:([\w\-\.\\\/]+).dev/, 'http:\1.dev')
|
31
|
+
end
|
32
|
+
|
33
|
+
def forward(pacto_request)
|
34
|
+
Pacto::Consumer::FaradayDriver.new.execute(pacto_request)
|
35
|
+
end
|
36
|
+
|
37
|
+
def prepare_to_respond(pacto_response)
|
38
|
+
pacto_response.headers.delete_if { |k, _v| %w(connection content-encoding content-length transfer-encoding).include? k.downcase }
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def host_for(pacto_request)
|
44
|
+
# FIXME: Need case insensitive fetch for headers
|
45
|
+
pacto_request.uri.site || pacto_request.headers.find { |key, _| key.downcase == 'host' }[1]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -3,7 +3,7 @@ module Pacto
|
|
3
3
|
module Server
|
4
4
|
module Settings
|
5
5
|
def options_parser(opts, options) # rubocop:disable MethodLength
|
6
|
-
options[:format] ||= :
|
6
|
+
options[:format] ||= :legacy
|
7
7
|
options[:strict] ||= false
|
8
8
|
options[:directory] ||= File.expand_path('contracts', @original_pwd)
|
9
9
|
options[:config] ||= File.expand_path('../config.rb', __FILE__)
|
@@ -24,6 +24,72 @@ module Pacto
|
|
24
24
|
opts.on('--stenographer-log-file', 'Location for the stenographer log file') { |val| options[:stenographer_log_file] = val }
|
25
25
|
opts.on('--log-level [LEVEL]', [:debug, :info, :warn, :error, :fatal], 'Pacto log level ( debug, info, warn, error or fatal)') { |val| options[:pacto_log_level] = val }
|
26
26
|
end
|
27
|
+
|
28
|
+
class OptionHandler
|
29
|
+
attr_reader :port, :logger, :config, :options
|
30
|
+
|
31
|
+
def initialize(port, logger, config = {})
|
32
|
+
@port, @logger, @config = port, logger, config
|
33
|
+
end
|
34
|
+
|
35
|
+
def token_map
|
36
|
+
if File.readable? '.tokens.json'
|
37
|
+
MultiJson.load(File.read '.tokens.json')
|
38
|
+
else
|
39
|
+
{}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def prepare_contracts(contracts)
|
44
|
+
contracts.stub_providers if options[:stub]
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle(options) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
48
|
+
@options = options
|
49
|
+
config[:backend_host] = options[:backend_host]
|
50
|
+
config[:strip_port] = options[:strip_port]
|
51
|
+
config[:strip_dev] = options[:strip_dev]
|
52
|
+
config[:port] = port
|
53
|
+
contracts_path = options[:directory] || File.expand_path('contracts', Dir.pwd)
|
54
|
+
Pacto.configure do |pacto_config|
|
55
|
+
pacto_config.logger = options[:pacto_logger] || logger
|
56
|
+
pacto_config.loggerl.log_level = config[:pacto_log_level] if config[:pacto_log_level]
|
57
|
+
pacto_config.contracts_path = contracts_path
|
58
|
+
pacto_config.strict_matchers = options[:strict]
|
59
|
+
pacto_config.generator_options = {
|
60
|
+
schema_version: :draft3,
|
61
|
+
token_map: token_map
|
62
|
+
}
|
63
|
+
pacto_config.stenographer_log_file = options[:stenographer_log_file]
|
64
|
+
end
|
65
|
+
|
66
|
+
if options[:generate]
|
67
|
+
Pacto.generate!
|
68
|
+
logger.info 'Pacto generation mode enabled'
|
69
|
+
end
|
70
|
+
|
71
|
+
if options[:recursive_loading]
|
72
|
+
Dir["#{contracts_path}/*"].each do |host_dir|
|
73
|
+
host = File.basename host_dir
|
74
|
+
prepare_contracts Pacto.load_contracts(host_dir, "https://#{host}", options[:format])
|
75
|
+
end
|
76
|
+
else
|
77
|
+
host_pattern = options[:backend_host] || '{scheme}://{server}'
|
78
|
+
if File.exist? contracts_path
|
79
|
+
prepare_contracts Pacto.load_contracts(contracts_path, host_pattern, options[:format])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
Pacto.validate! if options[:validate]
|
84
|
+
|
85
|
+
if options[:live]
|
86
|
+
# WebMock.reset!
|
87
|
+
WebMock.allow_net_connect!
|
88
|
+
end
|
89
|
+
|
90
|
+
config
|
91
|
+
end
|
92
|
+
end
|
27
93
|
end
|
28
94
|
end
|
29
95
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pacto-server
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.0.
|
4
|
+
version: 0.4.0.rc2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ThoughtWorks
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pacto
|
@@ -16,56 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.4.0.
|
19
|
+
version: 0.4.0.rc2
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.4.0.
|
26
|
+
version: 0.4.0.rc2
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: reel
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0.5'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: em-synchrony
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '1.0'
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '1.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: em-http-request
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '1.1'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '1.1'
|
40
|
+
version: '0.5'
|
69
41
|
description: Pacto Server let's you run Pacto as a standalone server to arbitrate
|
70
42
|
contract disputes between a service provider and one or more consumers in any programming
|
71
43
|
language. It's Pacto beyond Ruby
|
@@ -77,8 +49,9 @@ extensions: []
|
|
77
49
|
extra_rdoc_files: []
|
78
50
|
files:
|
79
51
|
- bin/pacto-server
|
80
|
-
- lib/pacto/server/
|
52
|
+
- lib/pacto/server/cli.rb
|
81
53
|
- lib/pacto/server/config.rb
|
54
|
+
- lib/pacto/server/proxy.rb
|
82
55
|
- lib/pacto/server/settings.rb
|
83
56
|
homepage: http://thoughtworks.github.io/pacto/
|
84
57
|
licenses:
|
data/lib/pacto/server/api.rb
DELETED
@@ -1,102 +0,0 @@
|
|
1
|
-
# -*- encoding : utf-8 -*-
|
2
|
-
require 'pacto/server/settings'
|
3
|
-
|
4
|
-
module Pacto
|
5
|
-
module Server
|
6
|
-
class API < Goliath::API
|
7
|
-
include Pacto::Server::Settings
|
8
|
-
use ::Rack::ContentLength
|
9
|
-
|
10
|
-
def initialize(*args)
|
11
|
-
@original_pwd = Dir.pwd
|
12
|
-
super
|
13
|
-
end
|
14
|
-
|
15
|
-
def on_headers(env, headers)
|
16
|
-
env.logger.debug 'receiving headers: ' + headers.inspect
|
17
|
-
env['client-headers'] = headers
|
18
|
-
end
|
19
|
-
|
20
|
-
def on_body(env, data)
|
21
|
-
env.logger.debug 'received data: ' + data
|
22
|
-
(env['async-body'] ||= '') << data
|
23
|
-
end
|
24
|
-
|
25
|
-
def response(env)
|
26
|
-
log_request(env)
|
27
|
-
req = prepare_pacto_request(env)
|
28
|
-
env.logger.info "sending: #{req}"
|
29
|
-
resp = Pacto::Consumer::FaradayDriver.new.execute(req)
|
30
|
-
process_pacto_response resp, env
|
31
|
-
rescue => e
|
32
|
-
backtrace = e.backtrace.join("\n")
|
33
|
-
env.logger.warn "responding with error: #{e.message}, backtrace: #{backtrace}"
|
34
|
-
[500, {}, e.message]
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def log_request(env)
|
40
|
-
method = env['REQUEST_METHOD'].upcase
|
41
|
-
env.logger.info "received: #{method} #{env['REQUEST_URI']} with headers #{env['client-headers']}"
|
42
|
-
end
|
43
|
-
|
44
|
-
def prepare_pacto_request(env)
|
45
|
-
PactoRequest.new(
|
46
|
-
body: env['async-body'],
|
47
|
-
headers: filter_request_headers(env),
|
48
|
-
method: env['REQUEST_METHOD'].downcase.to_sym,
|
49
|
-
uri: determine_proxy_uri(env)
|
50
|
-
)
|
51
|
-
end
|
52
|
-
|
53
|
-
def process_pacto_response(resp, _env)
|
54
|
-
code = resp.status
|
55
|
-
safe_response_headers = normalize_headers(resp.headers).reject { |k, _v| %w(connection content-encoding content-length transfer-encoding).include? k.downcase }
|
56
|
-
body = proxy_rewrite(resp.body)
|
57
|
-
env.logger.info "received response: #{resp.inspect}"
|
58
|
-
env.logger.debug "response body: #{body}"
|
59
|
-
[code, safe_response_headers, body]
|
60
|
-
end
|
61
|
-
|
62
|
-
def determine_proxy_uri(env)
|
63
|
-
path = env[Goliath::Request::REQUEST_PATH]
|
64
|
-
if env.config[:backend_host]
|
65
|
-
uri = Addressable::URI.heuristic_parse("#{env.config[:backend_host]}#{path}")
|
66
|
-
else
|
67
|
-
host = env['HTTP_HOST']
|
68
|
-
host.gsub!('.dev', '.com') if env.config[:strip_dev]
|
69
|
-
uri = Addressable::URI.heuristic_parse("https://#{host}#{path}")
|
70
|
-
uri.port = nil if env.config[:strip_port]
|
71
|
-
end
|
72
|
-
uri
|
73
|
-
end
|
74
|
-
|
75
|
-
def filter_request_headers(env)
|
76
|
-
headers = env['client-headers']
|
77
|
-
safe_headers = headers.reject { |k, _v| %w(host content-length transfer-encoding).include? k.downcase }
|
78
|
-
env.logger.debug "filtered headers: #{safe_headers}"
|
79
|
-
safe_headers
|
80
|
-
end
|
81
|
-
|
82
|
-
def port
|
83
|
-
env.config[:port]
|
84
|
-
end
|
85
|
-
|
86
|
-
def normalize_headers(headers)
|
87
|
-
headers.each_with_object({}) do |elem, res|
|
88
|
-
key = elem.first.dup
|
89
|
-
value = elem.last
|
90
|
-
key.gsub!('_', '-')
|
91
|
-
key = key.split('-').map(&:capitalize).join '-'
|
92
|
-
res[key] = value
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def proxy_rewrite(body)
|
97
|
-
# FIXME: How I usually deal with rels, but others may not want this behavior.
|
98
|
-
body.gsub('.com', ".dev:#{port}").gsub(/https\:([\w\-\.\\\/]+).dev/, 'http:\1.dev')
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|