infrataster 0.1.0

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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +133 -0
  6. data/Rakefile +1 -0
  7. data/example/.gitignore +4 -0
  8. data/example/.rspec +2 -0
  9. data/example/Berksfile +2 -0
  10. data/example/Gemfile +8 -0
  11. data/example/README.md +13 -0
  12. data/example/Vagrantfile +81 -0
  13. data/example/cookbooks/app/recipes/default.rb +36 -0
  14. data/example/cookbooks/db/files/default/my.cnf +127 -0
  15. data/example/cookbooks/db/recipes/default.rb +14 -0
  16. data/example/cookbooks/proxy/metadata.rb +1 -0
  17. data/example/cookbooks/proxy/recipes/default.rb +26 -0
  18. data/example/cookbooks/proxy/templates/default/app.erb +13 -0
  19. data/example/cookbooks/proxy/templates/default/static.erb +14 -0
  20. data/example/sinatra_app/.gitignore +1 -0
  21. data/example/sinatra_app/Gemfile +5 -0
  22. data/example/sinatra_app/app.rb +6 -0
  23. data/example/sinatra_app/config.ru +2 -0
  24. data/example/spec/spec_helper.rb +31 -0
  25. data/example/spec/web_spec.rb +64 -0
  26. data/infrataster.gemspec +30 -0
  27. data/lib/infrataster/browsermob_proxy.rb +31 -0
  28. data/lib/infrataster/contexts/base_context.rb +14 -0
  29. data/lib/infrataster/contexts/capybara_context.rb +56 -0
  30. data/lib/infrataster/contexts/http_context.rb +20 -0
  31. data/lib/infrataster/contexts/mysql_query_context.rb +26 -0
  32. data/lib/infrataster/contexts.rb +36 -0
  33. data/lib/infrataster/helpers/rspec_helper.rb +15 -0
  34. data/lib/infrataster/helpers/type_helper.rb +24 -0
  35. data/lib/infrataster/helpers.rb +4 -0
  36. data/lib/infrataster/rspec.rb +17 -0
  37. data/lib/infrataster/server.rb +125 -0
  38. data/lib/infrataster/types/base_type.rb +15 -0
  39. data/lib/infrataster/types/capybara_type.rb +21 -0
  40. data/lib/infrataster/types/http_type.rb +24 -0
  41. data/lib/infrataster/types/mysql_query_type.rb +21 -0
  42. data/lib/infrataster/types/server_type.rb +25 -0
  43. data/lib/infrataster/types.rb +5 -0
  44. data/lib/infrataster/version.rb +3 -0
  45. data/lib/infrataster.rb +12 -0
  46. metadata +214 -0
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'infrataster/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "infrataster"
8
+ spec.version = Infrataster::VERSION
9
+ spec.authors = ["Ryota Arai"]
10
+ spec.email = ["ryota.arai@gmail.com"]
11
+ spec.summary = %q{Infrastructure Behavior Testing Framework}
12
+ spec.homepage = "https://github.com/ryotarai/infrataster"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_runtime_dependency "rspec"
21
+ spec.add_runtime_dependency "net-ssh"
22
+ spec.add_runtime_dependency "net-ssh-gateway"
23
+ spec.add_runtime_dependency "mysql2"
24
+ spec.add_runtime_dependency "capybara"
25
+ spec.add_runtime_dependency "selenium-webdriver"
26
+ spec.add_runtime_dependency "browsermob-proxy"
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.5"
29
+ spec.add_development_dependency "rake"
30
+ end
@@ -0,0 +1,31 @@
1
+ require 'browsermob/proxy'
2
+
3
+ module Infrataster
4
+ module BrowsermobProxy
5
+ class << self
6
+ def server
7
+ @server ||= start_server
8
+ end
9
+
10
+ def bin_path
11
+ @bin_path
12
+ end
13
+
14
+ def bin_path=(path)
15
+ @bin_path = path
16
+ end
17
+
18
+ private
19
+ def start_server
20
+ BrowserMob::Proxy::Server.new(find_bin).tap do |server|
21
+ server.start
22
+ end
23
+ end
24
+
25
+ def find_bin
26
+ return @bin_path if @bin_path
27
+ `which browsermob-proxy`
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ module Infrataster
2
+ module Contexts
3
+ class BaseContext
4
+ attr_reader :server
5
+ attr_reader :type
6
+
7
+ def initialize(server, type)
8
+ @server = server
9
+ @type = type
10
+ end
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,56 @@
1
+ require 'capybara'
2
+ require 'capybara/rspec/matchers'
3
+ require 'selenium-webdriver'
4
+
5
+ module Infrataster
6
+ module Contexts
7
+ class CapybaraContext < BaseContext
8
+ def initialize(*args)
9
+ super(*args)
10
+
11
+ register_capybara_driver
12
+ end
13
+
14
+ def session
15
+ return @session if @session
16
+
17
+ address, port, @gateway_finalize_proc = server.from_gateway(type.uri.port)
18
+ Capybara.app_host = "http://#{address}:#{port}"
19
+ @session = Capybara::Session.new(capybara_driver_name)
20
+ end
21
+
22
+ def page
23
+ session
24
+ end
25
+
26
+ def before_each(example)
27
+ example.example_group_instance.extend(Capybara::RSpecMatchers)
28
+ end
29
+
30
+ def after_each(example)
31
+ @gateway_finalize_proc.call if @gateway_finalize_proc
32
+ end
33
+
34
+ def method_missing(method, *args)
35
+ session.public_send(method, *args)
36
+ end
37
+
38
+ private
39
+ def register_capybara_driver
40
+ proxy = BrowsermobProxy.server.create_proxy
41
+ proxy.header({"Host" => type.uri.host})
42
+
43
+ profile = Selenium::WebDriver::Firefox::Profile.new
44
+ profile.proxy = proxy.selenium_proxy
45
+
46
+ Capybara.register_driver capybara_driver_name do |app|
47
+ Capybara::Selenium::Driver.new(app, profile: profile)
48
+ end
49
+ end
50
+
51
+ def capybara_driver_name
52
+ :"selenium_via_proxy_#{hash}"
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,20 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+
4
+ module Infrataster
5
+ module Contexts
6
+ class HttpContext < BaseContext
7
+ def response
8
+ req = Net::HTTP::Get.new('/', {'Host' => type.uri.host})
9
+
10
+ server.from_gateway(type.uri.port) do |address, port|
11
+ Net::HTTP.start(address, port) do |http|
12
+ http.request(req)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+
@@ -0,0 +1,26 @@
1
+ require 'mysql2'
2
+
3
+ module Infrataster
4
+ module Contexts
5
+ class MysqlQueryContext < BaseContext
6
+ def results
7
+ options = {port: 3306, user: 'root', password: ''}
8
+ if server.options[:mysql]
9
+ options = options.merge(server.options[:mysql])
10
+ end
11
+
12
+ server.from_gateway(options[:port]) do |address, new_port|
13
+ client = Mysql2::Client.new(
14
+ host: address,
15
+ port: new_port,
16
+ username: options[:user],
17
+ password: options[:password],
18
+ )
19
+ client.query(type.query)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+
@@ -0,0 +1,36 @@
1
+ require 'infrataster/types'
2
+ require 'infrataster/contexts/base_context'
3
+ require 'infrataster/contexts/http_context'
4
+ require 'infrataster/contexts/mysql_query_context'
5
+ require 'infrataster/contexts/capybara_context'
6
+
7
+ module Infrataster
8
+ module Contexts
9
+ class << self
10
+ def from_example(example)
11
+ example_group = example.metadata[:example_group]
12
+
13
+ server = find_described(Types::ServerType, example_group).server
14
+ type = find_described(Types::BaseType, example_group)
15
+
16
+ type.context_class.new(server, type)
17
+ end
18
+
19
+ private
20
+ def find_described(type_class, example_group)
21
+ arg = example_group[:description_args].first
22
+ if arg.is_a?(type_class)
23
+ arg
24
+ else
25
+ parent_example_group = example_group[:example_group]
26
+ if parent_example_group
27
+ find_described(type_class, parent_example_group)
28
+ else
29
+ raise Error
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,15 @@
1
+ module Infrataster
2
+ module Helpers
3
+ module RSpecHelper
4
+ def method_missing(method, *args)
5
+ current_infrataster_context.public_send(method, *args)
6
+ end
7
+
8
+ def current_infrataster_context
9
+ @infrataster_context
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+
@@ -0,0 +1,24 @@
1
+ require 'infrataster/types'
2
+
3
+ module Infrataster
4
+ module Helpers
5
+ module TypeHelper
6
+ def server(*args)
7
+ Types::ServerType.new(*args)
8
+ end
9
+
10
+ def http(*args)
11
+ Types::HttpType.new(*args)
12
+ end
13
+
14
+ def mysql_query(*args)
15
+ Types::MysqlQueryType.new(*args)
16
+ end
17
+
18
+ def capybara(*args)
19
+ Types::CapybaraType.new(*args)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,4 @@
1
+ require 'infrataster/helpers/type_helper'
2
+ require 'infrataster/helpers/rspec_helper'
3
+
4
+
@@ -0,0 +1,17 @@
1
+ require 'infrataster'
2
+ require 'rspec'
3
+
4
+ include Infrataster::Helpers::TypeHelper
5
+
6
+ RSpec.configure do |config|
7
+ config.include Infrataster::Helpers::RSpecHelper
8
+
9
+ config.before(:each) do
10
+ @infrataster_context = Infrataster::Contexts.from_example(example)
11
+ @infrataster_context.before_each(example) if @infrataster_context.respond_to?(:before_each)
12
+ end
13
+ config.after(:each) do
14
+ @infrataster_context.after_each(example) if @infrataster_context.respond_to?(:after_each)
15
+ end
16
+ end
17
+
@@ -0,0 +1,125 @@
1
+ require 'tmpdir'
2
+ require 'net/ssh'
3
+ require 'net/ssh/gateway'
4
+ require 'ipaddr'
5
+
6
+ module Infrataster
7
+ class Server
8
+ Error = Class.new(StandardError)
9
+
10
+ class << self
11
+ @@servers = []
12
+
13
+ def define(*args)
14
+ @@servers << Server.new(*args)
15
+ end
16
+
17
+ def find_by_name(name)
18
+ server = @@servers.find {|s| s.name == name }
19
+ unless server
20
+ raise Error, "Server definition for '#{name}' is not found."
21
+ end
22
+ server
23
+ end
24
+
25
+ end
26
+
27
+ attr_reader :name, :address, :options
28
+
29
+ def initialize(name, address, options = {})
30
+ @name, @address, @options = name, address, options
31
+ end
32
+
33
+ def from
34
+ if @options[:from]
35
+ Server.find_by_name(@options[:from])
36
+ end
37
+ end
38
+
39
+ def ssh_gateway
40
+ gateway = Net::SSH::Gateway.new(*ssh_start_args)
41
+ finalize_proc = Proc.new do
42
+ gateway.shutdown!
43
+ end
44
+
45
+ [gateway, finalize_proc]
46
+ end
47
+
48
+ def with_ssh_gateway
49
+ gateway, finalize_proc = ssh_gateway
50
+ yield gateway
51
+ ensure
52
+ finalize_proc.call
53
+ end
54
+
55
+ def gateway_open(*args)
56
+ if block_given?
57
+ with_ssh_gateway do |gateway|
58
+ gateway.open(*args) do |port|
59
+ yield port
60
+ end
61
+ end
62
+ else
63
+ gateway, gateway_finalize_proc = ssh_gateway
64
+ port = gateway.open(*args)
65
+ finalize_proc = Proc.new do
66
+ gateway.close(port)
67
+ gateway_finalize_proc.call
68
+ end
69
+ [port, finalize_proc]
70
+ end
71
+ end
72
+
73
+ def from_gateway(port)
74
+ if from
75
+ if block_given?
76
+ from.gateway_open(@address, port) do |new_port|
77
+ Logger.debug("tunnel: localhost:#{new_port} -> #{from.address} -> #{@address}:#{port}")
78
+ yield '127.0.0.1', new_port
79
+ end
80
+ else
81
+ new_port, finalize_proc = from.gateway_open(@address, port)
82
+ Logger.debug("tunnel: localhost:#{new_port} -> #{from.address} -> #{@address}:#{port}")
83
+ ['127.0.0.1', new_port, finalize_proc]
84
+ end
85
+ else
86
+ if block_given?
87
+ yield @address, port
88
+ else
89
+ [@address, port, nil]
90
+ end
91
+ end
92
+ end
93
+
94
+ def ssh_start_args
95
+ @ssh_start_args ||= _ssh_start_args
96
+ end
97
+
98
+ private
99
+
100
+
101
+ def _ssh_start_args
102
+ config = {}
103
+ if @options[:ssh]
104
+ config = @options[:ssh]
105
+ elsif @options[:vagrant]
106
+ if @options[:vagrant] != true
107
+ vagrant_name = @options[:vagrant]
108
+ else
109
+ vagrant_name = @name
110
+ end
111
+
112
+ Dir.mktmpdir do |dir|
113
+ output = File.join(dir, 'ssh-config')
114
+ system("/usr/bin/vagrant ssh-config #{vagrant_name} > #{output}")
115
+ config = Net::SSH::Config.for(@name.to_s, [output])
116
+ end
117
+ else
118
+ raise Error, "Can't get configuration to connect to the server via SSH. Please set ssh option or vagrant option."
119
+ end
120
+
121
+ [config[:host], config[:user], config]
122
+ end
123
+ end
124
+ end
125
+
@@ -0,0 +1,15 @@
1
+ module Infrataster
2
+ module Types
3
+ class BaseType
4
+ # do nothing
5
+ def name
6
+ self.class.name.split('::').last[0...-4]
7
+ end
8
+
9
+ def context_class
10
+ Contexts.const_get("#{name}Context")
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,21 @@
1
+ require 'infrataster/types/base_type'
2
+ require 'uri'
3
+
4
+ module Infrataster
5
+ module Types
6
+ class CapybaraType < BaseType
7
+ Error = Class.new(StandardError)
8
+
9
+ attr_reader :uri
10
+
11
+ def initialize(url)
12
+ @uri = URI.parse(url)
13
+ end
14
+
15
+ def to_s
16
+ "capybara '#{@uri}'"
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,24 @@
1
+ require 'infrataster/types/base_type'
2
+ require 'uri'
3
+
4
+ module Infrataster
5
+ module Types
6
+ class HttpType < BaseType
7
+ Error = Class.new(StandardError)
8
+
9
+ attr_reader :uri
10
+
11
+ def initialize(url_str)
12
+ @uri = URI.parse(url_str)
13
+ unless %w!http https!.include?(@uri.scheme)
14
+ raise Error, "The provided url, '#{@uri}', is not http or https."
15
+ end
16
+ end
17
+
18
+ def to_s
19
+ "http '#{@uri}'"
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,21 @@
1
+ require 'infrataster/types/base_type'
2
+
3
+ module Infrataster
4
+ module Types
5
+ class MysqlQueryType < BaseType
6
+ Error = Class.new(StandardError)
7
+
8
+ attr_reader :query
9
+
10
+ def initialize(query, options = {})
11
+ @query = query
12
+ @options = options
13
+ end
14
+
15
+ def to_s
16
+ "mysql '#{@query}'"
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,25 @@
1
+ module Infrataster
2
+ module Types
3
+ class ServerType
4
+ Error = Class.new(StandardError)
5
+
6
+ attr_reader :name
7
+
8
+ def initialize(name)
9
+ @name = name
10
+ end
11
+
12
+ def to_s
13
+ desc = "server '#{server.name}'"
14
+ desc += " from '#{server.from.name}'" if server.from
15
+ desc
16
+ end
17
+
18
+ def server
19
+ Server.find_by_name(@name)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+
@@ -0,0 +1,5 @@
1
+ require 'infrataster/types/base_type'
2
+ require 'infrataster/types/server_type'
3
+ require 'infrataster/types/http_type'
4
+ require 'infrataster/types/mysql_query_type'
5
+ require 'infrataster/types/capybara_type'
@@ -0,0 +1,3 @@
1
+ module Infrataster
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,12 @@
1
+ require "infrataster/version"
2
+ require "infrataster/types"
3
+ require "infrataster/server"
4
+ require "infrataster/helpers"
5
+ require "infrataster/browsermob_proxy"
6
+ require "infrataster/contexts"
7
+ require 'logger'
8
+
9
+ module Infrataster
10
+ Logger = ::Logger.new($stdout)
11
+ Logger.level = ::Logger::ERROR
12
+ end