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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +133 -0
- data/Rakefile +1 -0
- data/example/.gitignore +4 -0
- data/example/.rspec +2 -0
- data/example/Berksfile +2 -0
- data/example/Gemfile +8 -0
- data/example/README.md +13 -0
- data/example/Vagrantfile +81 -0
- data/example/cookbooks/app/recipes/default.rb +36 -0
- data/example/cookbooks/db/files/default/my.cnf +127 -0
- data/example/cookbooks/db/recipes/default.rb +14 -0
- data/example/cookbooks/proxy/metadata.rb +1 -0
- data/example/cookbooks/proxy/recipes/default.rb +26 -0
- data/example/cookbooks/proxy/templates/default/app.erb +13 -0
- data/example/cookbooks/proxy/templates/default/static.erb +14 -0
- data/example/sinatra_app/.gitignore +1 -0
- data/example/sinatra_app/Gemfile +5 -0
- data/example/sinatra_app/app.rb +6 -0
- data/example/sinatra_app/config.ru +2 -0
- data/example/spec/spec_helper.rb +31 -0
- data/example/spec/web_spec.rb +64 -0
- data/infrataster.gemspec +30 -0
- data/lib/infrataster/browsermob_proxy.rb +31 -0
- data/lib/infrataster/contexts/base_context.rb +14 -0
- data/lib/infrataster/contexts/capybara_context.rb +56 -0
- data/lib/infrataster/contexts/http_context.rb +20 -0
- data/lib/infrataster/contexts/mysql_query_context.rb +26 -0
- data/lib/infrataster/contexts.rb +36 -0
- data/lib/infrataster/helpers/rspec_helper.rb +15 -0
- data/lib/infrataster/helpers/type_helper.rb +24 -0
- data/lib/infrataster/helpers.rb +4 -0
- data/lib/infrataster/rspec.rb +17 -0
- data/lib/infrataster/server.rb +125 -0
- data/lib/infrataster/types/base_type.rb +15 -0
- data/lib/infrataster/types/capybara_type.rb +21 -0
- data/lib/infrataster/types/http_type.rb +24 -0
- data/lib/infrataster/types/mysql_query_type.rb +21 -0
- data/lib/infrataster/types/server_type.rb +25 -0
- data/lib/infrataster/types.rb +5 -0
- data/lib/infrataster/version.rb +3 -0
- data/lib/infrataster.rb +12 -0
- metadata +214 -0
data/infrataster.gemspec
ADDED
@@ -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,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,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,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,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
|
+
|
data/lib/infrataster.rb
ADDED
@@ -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
|