pacto-server 0.4.0.rc1 → 0.4.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 118b6d643b8f3a10e313cf65837f2f58d03f0d44
4
- data.tar.gz: 063e07bd7f75b897ffe798c9591f43d67ac624bd
3
+ metadata.gz: 48d92c082f19c254b64fa04ff72916ca26006864
4
+ data.tar.gz: dfdbd70436fe3d864b3a6743fdf150be10964e75
5
5
  SHA512:
6
- metadata.gz: cef2e211096315d6b92487bddc64bb3401f2ece8fe4cc777314ab798125117073fe0084189f4b0c09be572f77033bc83a6a1630872a8d6f934f5ed820950eb68
7
- data.tar.gz: 54a2f790dbe2489fc0470b285f65cf6e2de8ca584e7f0a1067be6bf77e03af208530cf28dda580e19d953cdbd578f9fc24510378b8ed06907d45111ae05f3e81
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 'goliath/api'
3
- require 'goliath/runner'
4
- require 'pacto/server'
2
+ require 'pacto/server/cli'
5
3
 
6
- runner = Goliath::Runner.new(ARGV, Pacto::Server::API.new(:pwd => Dir.pwd))
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
@@ -1,51 +1,2 @@
1
1
  # -*- encoding : utf-8 -*-
2
- def token_map
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] ||= :default
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.rc1
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: 2014-11-18 00:00:00.000000000 Z
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.rc1
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.rc1
26
+ version: 0.4.0.rc2
27
27
  - !ruby/object:Gem::Dependency
28
- name: goliath
28
+ name: reel
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.0'
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: '1.0'
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/api.rb
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:
@@ -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